13 Commits

Author SHA1 Message Date
36356093ff Add video examples 2025-06-20 20:32:06 -04:00
03e9317041 Rename folder for clarity 2025-06-20 19:59:32 -04:00
b6c684cdd1 Simplify startup 2025-06-20 13:30:10 -04:00
8e2cd7f9a1 Cleanup 2025-06-20 12:48:25 -04:00
fa25dde20f Add linkedin link 2025-06-20 12:46:12 -04:00
1696808c00 Fix devcontainer setup 2025-06-20 10:47:20 -04:00
7aa80d9800 Cleanup backend dockerfile 2025-06-19 21:14:49 -04:00
761ea8fef8 Simplify slides 2025-06-19 13:05:44 -04:00
94a71d6d77 Clean up compose 2025-06-19 12:26:09 -04:00
66fc64dcac Update slides and cleanup examples 2025-06-19 12:05:32 -04:00
3f0d1d77ca Update links to repo 2025-06-13 20:32:13 -04:00
7c6e8a88a3 Add demo on devcontainer 2025-06-13 20:12:08 -04:00
302cc66d55 Restructure and clarify 2025-06-13 16:13:57 -04:00
77 changed files with 237 additions and 215 deletions

View File

@ -1,11 +1,21 @@
# Welcome to [Slidev](https://github.com/slidevjs/slidev)!
# Demystifying Docker
This repo is a work in progress documenting my upcoming presentation for Scenic City Summit 2025. This repo is currently split into 2 parts.
To start the slide show:
## Slide Deck presentaiton
The slides folder contains my slide deck for the event. To view the presentation:
- `pnpm install`
- `pnpm dev`
- visit <http://localhost:3030>
```shell
cd slides
yarn
yarn dev
```
Once everything is done it should open a browser window to http://localhost:3030. I am using [slidev](https://sli.dev) for the slide deck. All of the "slides" are in a single markdown file in the slides directory.
## Code examples
The examples folder contains several Docker examples.
`examples/fullstack` is a basic example containing a backend NodeJS application with a React frontend as well as a Postgres Database. The project is set up so that you can do all development in dev containers. The `node_modules` folder for the backend is isolated from your host system and only exists inside the running container. For this reason, you will need to use a devcontainer in order to be able to get things like Intellisense and typings.
Edit the [slides.md](./slides.md) to see the changes.
Learn more about Slidev at the [documentation](https://sli.dev/).

Binary file not shown.

View File

@ -0,0 +1,18 @@
{
"name": "Fullstack DevContainer",
"dockerComposeFile": ["../compose.yml", "../compose.override.yml"],
"service": "backend",
"workspaceFolder": "/app",
"shutdownAction": "stopCompose",
"forwardPorts": [80, 8080],
"postCreateCommand": "/entrypoint.sh",
"customizations": {
"vscode": {
"extensions": [
"jripouteau.adonis-vscode-extension",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}
}
}

View File

@ -0,0 +1,49 @@
# Overview
This is a practical example of a "basic" fullstack application. This application has been fully containerized to be run locally in development mode as well as for deployment to production. It is composed of the following services:
- frontend - Basic React app bootstrapped with `yarn create vite`
- backend - AdonisJS API Backend [https://adonisjs.com]()
- database - Postgres database
- reverse_proxy - Traefik ingress controller handling reverse proxy for the frontend and backend applications.
## Getting Started
### Devcontainer
You can run this project via dev containers. First you will need to ensure that you have the [VSCode dev containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
Once you have the extension installed, there are a couple ways to get started. From this directory run `code .` to open a new VSCode window targeting this directory. After a few seconds you should get a notification in the bottom right asking if you want to reopen the workspace in a container. This will open a new VScode window and will start the process of building and running all of the containers.
<video src="./open-devcontainers-option1.m4v" controls></video>
Another method is to open the `Command Palette` (CMD+Shift+P) and search for `Dev Containers: Open Folder in Dev Container`. Select this folder and VScode will open a new window and kick off the process. The very first time you do this it will take several minutes to build the images and start the containers.
<video src="./open-devcontainers-option2.m4v" controls></video>
Alternatively, you can also just use the compose files to start a stack and connect your VSCode instance to the backend container.
To do that, clone the repo and `cd` into this directory.
```shell
cd examples/devcontainers
docker compose up
```
The first time you run `docker compose up` it may take a few minutes as Docker will need to build images for the frontend and backend. Also there are some database migrations and seed data that need to happen. Those are handled by `backend/entrypoint.sh`. This is handled for you automatically since it is baked into the image. Once everything is up you should be able to access the frontend at [http://app.docker.localhost:8888]() and the backend at [http://app.docker.localhost:8888/api]()
![](./homepage.png)
## Developing
When running the `compose` stack, the backend/frontend node_modules are isolated from your host system. This is common in dev environment setups. In this case it is advised to use a Dev container. The following video demonstrates starting a dev container by using the VSCode plugin.
<video src="../../assets/open-dev-container.m4v" controls></video>
### Proxying and routes
You will notice that the backend is listening on `http://0.0.0.0:3333/` but the frontend is making requests to `/api/`. This is designed to mimic how you would deploy something like this in production where the backend would be behind a reverse proxy. Traefik has some really nice middleware that allows us to easily tell it to route requests destined for `/api` to `/`. The bit that handles that are these labels:
```compose
labels:
- "traefik.http.middlewares.strip-api-prefix.stripprefix.prefixes=/api"
- "traefik.http.routers.backend.rule=Host(`app.docker.localhost`) && PathPrefix(`/api`)"
- "traefik.http.routers.backend.middlewares=strip-api-prefix@docker"
```
We are defining a middleware named `strip-api-prefix` using the built in `strippreffix` Traefik middleware. We are then telling it to remove `/api` from any requests it handles. We then attach that to our backend router.

View File

@ -1,33 +1,33 @@
FROM node:lts-alpine3.22 AS base
HEALTHCHECK --interval=5s --timeout=10s --start-period=5s --retries=5 \
HEALTHCHECK --interval=5s \
--timeout=10s \
--start-period=5s \
--retries=5 \
CMD sh -c 'wget --no-verbose --tries=1 --spider http://127.0.0.1:3333 || exit 1'
# All deps stage
FROM base AS deps
WORKDIR /app
WORKDIR /app/backend
ADD package.json package-lock.json ./
RUN npm ci
FROM deps AS develop
WORKDIR /app
COPY dev-entrypoint.sh /entrypoint.sh
COPY .env.example /app/.env
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
RUN cat /entrypoint.sh
ENV NODE_ENV=development
EXPOSE 3333
ENTRYPOINT [ "/entrypoint.sh" ]
# Production only deps stage
FROM base AS production-deps
WORKDIR /app
WORKDIR /app/backend
ADD package.json package-lock.json ./
RUN npm ci --omit=dev
# Build stage
FROM base AS build
WORKDIR /app
COPY --from=deps /app/node_modules /app/node_modules
WORKDIR /app/backend
COPY --from=deps /app/backend/node_modules /app/backend/node_modules
ADD . .
RUN node ace build
@ -35,8 +35,8 @@ RUN node ace build
FROM base
ENV NODE_ENV=production
WORKDIR /app
COPY --from=production-deps /app/node_modules /app/node_modules
COPY --from=build /app/build /app
COPY --from=production-deps /app/backend/node_modules /app/backend/node_modules
COPY --from=build /app/backend/build /app
EXPOSE 8080
CMD ["node", "./bin/server.js"]

View File

@ -0,0 +1,8 @@
import type { HttpContext } from '@adonisjs/core/http'
import User from '#models/user'
export default class UsersController {
public async index({ request }: HttpContext) {
const page = request.input('page', 1)
return User.query().paginate(page)
}
}

View File

@ -1,9 +1,10 @@
#!/bin/sh
cd /app/backend
cp .env.example .env
echo "starting up..."
node ace generate:key
# Check for pending migrations by parsing output
PENDING_MIGRATIONS=$(node ace migration:status | grep -ic 'pending')

View File

@ -8,7 +8,7 @@
*/
import router from '@adonisjs/core/services/router'
import UsersController from '#controllers/users_controller'
const UsersController = () => import('#controllers/users_controller')
router.get('users', [UsersController, 'index'])
router.get('/', async () => {
return {

View File

@ -0,0 +1,17 @@
---
services:
backend:
build:
context: backend
target: develop
volumes:
- ./backend:/app/backend/
- node_modules:/app/backend/node_modules
- frontend_node_modules:/app/frontend/node_modules
- ./frontend/:/app/frontend/
depends_on:
db:
condition: service_healthy
volumes:
node_modules: {}
frontend_node_modules: {}

View File

@ -1,3 +1,4 @@
name: fullstack
services:
frontend:
image: frontend:latest
@ -5,8 +6,8 @@ services:
context: ./frontend
target: develop
volumes:
- frontend_node_modules:/app/node_modules
- ./frontend/src:/app/src
- frontend_node_modules:/app/frontend/node_modules
- ./frontend/:/app/frontend/
labels:
- "traefik.http.routers.app.rule=Host(`app.docker.localhost`)"
reverse-proxy:
@ -22,16 +23,10 @@ services:
image: backend:latest
build:
context: ./backend
volumes:
- backend_node_modules:/backend/node_modules
- ./backend/:/backend
labels:
- "traefik.http.middlewares.strip-api-prefix.stripprefix.prefixes=/api"
- "traefik.http.routers.backend.rule=Host(`app.docker.localhost`) && PathPrefix(`/api`)"
- "traefik.http.routers.backend.middlewares=strip-api-prefix@docker"
env_file:
- ./backend/.env
depends_on:
db:
condition: service_healthy

View File

@ -1,19 +1,16 @@
FROM node:22-alpine AS base
WORKDIR /app
WORKDIR /app/frontend
COPY package.json yarn.lock ./
RUN yarn install
COPY . .
FROM base AS build
WORKDIR /app
COPY --from=base /app/* .
RUN yarn build
FROM base AS develop
COPY --from=base /app/ .
EXPOSE 5173
ENTRYPOINT [ "yarn", "dev", "--host", "0.0.0.0" ]
FROM nginx:alpine AS production
COPY --from=build /app/dist/ /usr/share/nginx/html
COPY --from=build /app/frontend/dist/ /usr/share/nginx/html
ENTRYPOINT [ "nginx", "-g", "daemon off;" ]

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -18,21 +18,6 @@
filter: drop-shadow(0 0 2em #61dafbaa);
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}
.card {
padding: 2em;
}

View File

@ -1,7 +1,6 @@
import { useState, useEffect } from 'react'
import reactLogo from './assets/react.svg'
import dockerLogo from './assets/docker.svg'
import viteLogo from '/vite.svg'
import './App.css'
function App() {
@ -38,9 +37,6 @@ function App() {
return (
<>
<div>
<a href="https://vite.dev" target="_blank" rel="noreferrer">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank" rel="noreferrer">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
@ -49,7 +45,7 @@ function App() {
</a>
</div>
<h1>Vite + React + Docker + Traefik</h1>
<h1>React + Docker + Traefik</h1>
<table>
<thead>

View File

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

Before

Width:  |  Height:  |  Size: 170 KiB

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

Binary file not shown.

View File

@ -1,25 +0,0 @@
{
"name": "Fullstack DevContainer",
"dockerComposeFile": ["../docker-compose.yml"],
"service": "frontend",
"workspaceFolder": "/app",
"shutdownAction": "stopCompose",
"forwardPorts": [80, 8080],
"settings": {
"terminal.integrated.defaultProfile.linux": "bash"
},
"mounts": [
"source=frontend_node_modules,target=/app/node_modules,type=volume",
"source=backend_node_modules,target=/backend/node_modules,type=volume",
"source=${localWorkspaceFolder}/backend,target=/backend,type=bind"
],
"postCreateCommand": "npm install && cd /backend && npm install",
"customizations": {
"vscode": {
"extensions": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}
}
}

View File

@ -1,32 +0,0 @@
# Overview
This is a practical example of a "basic" fullstack application. This application has been fully containerized to be run locally in development mode as well as for deployment to production. It is composed of the following services:
- frontend - Basic React app bootstrapped with `yarn create vite`
- backend - AdonisJS API Backend [https://adonisjs.com]()
- database - Postgres database
- reverse_proxy - Traefik ingress controller handling reverse proxy for the frontend and backend applications.
## Getting Started
Clone the repo and `cd` into the `compose` directory.
```shell
cp backend/.env.example backend/.env
docker compose up
```
The first time you run `docker compose up` it may take a few minutes as Docker will need to build images for the frontend and backend. Also there are some database migrations and seed data that need to happen. Those are handled by `backend/dev-entrypoint.sh`. This is handled for you automatically since it is baked into the image. You will most likely need to run `sudo docker compose up` or add yourself to the privileged `docker` group (`sudo usermod -aG docker YOUR_USER`) in order for `Traefik` to bind to port 80.
![](./homepage.png)
### Proxying and routes
You will notice that the backend is listening on `http://0.0.0.0:3333/` but the frontend is making requests to `/api/`. This is designed to mimic how you would deploy something like this in production where the backend would be behind a reverse proxy. Traefik has some really nice middleware that allows us to easily tell it to route requests destined for `/api` to `/`. The bit that handles that are these labels:
```compose
labels:
- "traefik.http.middlewares.strip-api-prefix.stripprefix.prefixes=/api"
- "traefik.http.routers.backend.rule=Host(`app.docker.localhost`) && PathPrefix(`/api`)"
- "traefik.http.routers.backend.middlewares=strip-api-prefix@docker"
```
We are defining a middleware named `strip-api-prefix` using the built in `strippreffix` Traefik middleware. We are then telling it to remove `/api` from any requests it handles. We then attach that to our backend router.

View File

@ -1,8 +0,0 @@
import type { HttpContext } from '@adonisjs/core/http'
import User from '#models/user'
export default class UsersController {
public async index({ request }: HttpContext) {
const page = request.input('page', 1)
return User.query().paginate(page)
}
}

View File

@ -1,14 +0,0 @@
---
services:
backend:
build:
context: backend
target: develop
volumes:
- ./backend:/app
- node_modules:/app/node_modules
depends_on:
db:
condition: service_healthy
volumes:
node_modules: {}

11
slides/README.md Normal file
View File

@ -0,0 +1,11 @@
# Welcome to [Slidev](https://github.com/slidevjs/slidev)!
To start the slide show:
- `pnpm install`
- `pnpm dev`
- visit <http://localhost:3030>
Edit the [slides.md](./slides.md) to see the changes.
Learn more about Slidev at the [documentation](https://sli.dev/).

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

View File

@ -5,15 +5,13 @@ theme: seriph
# like them? see https://unsplash.com/collections/94734566/slidev
background: https://cover.sli.dev
# some information about your slides (markdown enabled)
title: Welcome to Slidev
title: Demystifying Docker - SCS 2025
info: |
## Slidev Starter Template
Presentation slides for developers.
## The basics of containerization and beyond by Mike Conrad
Scenic City Summit 2025
Learn more at [Sli.dev](https://sli.dev)
# apply unocss classes to the current slide
Learn more at [Hackanooga](https://hackanooga.com/scs)
class: text-center
# https://sli.dev/features/drawing
drawings:
persist: false
# slide transition: https://sli.dev/guide/animations.html#slide-transitions
@ -29,14 +27,13 @@ mdc: true
Mike Conrad - SCS 2025
<div @click="$slidev.nav.next" class="mt-12 py-1 flex" hover:bg="white op-10">
<div @click="$slidev.nav.next" class="mt-12 py-1 flex justify-center flex-col">
<!-- Press Space for next page <carbon:arrow-right /> -->
<div class="ml-auto">
<a href="https://git.hackanooga.com/mikeconrad/demystifying-docker.get">Example Repo<br /> (with slides)</a>
<br />
<br>
<img src="./images/talk.jpg">
</div>
Follow along
<p><a href="https://hackanooga.com/scs">https://hackanooga.com/scs</a></p>
<small>Includes slide deck, and repo with examples</small>
<img src="./images/talk.jpg" width="200">
Connect with me on LinkedIn: https://linkedin.com/enxoco
</div>
<div class="abs-br m-6 text-xl">
@ -54,23 +51,21 @@ The last comment block of each slide will be treated as slide notes. It will be
---
transition: fade-out
layout: center
layout: statement
background: ./images/pexels-markusspiske-1089438.jpg
---
# Who is this for?
## About you
- Some experience with Docker/containers
- Some experience with BASH
- Want to better understand how containers work
---
transition: fade-out
layout: center
---
## Follow Along
**Example Repo** - https://git.hackanooga.com/mikeconrad/demystifying-docker
# The 3 universal constants in programming
<v-click>
<h2>1) The speed of light</h2>
</v-click>
<v-click>
<h2>2) "It's more complicated than you think"</h2>
</v-click>
<v-click>
<h2>3) "It works on my machine"</h2>
<br />
<small>Source: <a href="https://www.linkedin.com/posts/robertroskam_the-3-universal-constants-in-programming-activity-7339260450074775553-ofik?utm_source=share&utm_medium=member_desktop&rcm=ACoAACZXneYB_uWiOE0T9VO3caUkn7m0ZMrRS_o">Some random guy on the internet</a></small>
</v-click>
---
transition: fade-out
@ -79,18 +74,42 @@ layout: center
<img src="./images/docker-meme.jpg" width="300"/>
---
transition: fade-out
layout: center
layout: image-right
image: ./images/pexels-markusspiske-1089438.jpg
---
## Common Use cases for containers
- Reproducible dev environments
- Testing in CI/CD environments
- Better "Portability" of application code
- Snapshot of application code at specific point in time
# Who is this for?
## About you
- Some experience with Docker/containers
- Familiarity with Linux/BASH/zsh, etc
- Want to better understand how containers work
- Want to learn new techniques for automation
---
transition: fade-out
layout: image-left
image: ./images/pexels-joshsorenson-1714208.jpg
---
## Follow Along
<small>Visit the link to check out the sample Git repository.</small>
**Example Repo** - https://hackanooga.com/scs
**Prerequisites**
- Docker Engine (Linux) or Docker Desktop (Windows/MacOS)
- VSCode
- Git
- yarn, npm or pnpm (for viewing slides)
### VSCode plugins
- [Official Docker Plugin](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-docker)
- [Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
- [Container Tools](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-containers)
---
transition: fade-out
@ -115,13 +134,13 @@ layout: center
## Containers vs Virtual Machines
| Feature | VM | Container |
|------------------|----------------|------------------|
| Feature | VMs | Containers |
|------------------|-----------------------|------------------------------|
| Boot time | Minutes | Seconds |
| Resource usage | Heavy | Lightweight |
| Isolation | Strong | Process-level |
| Portability | Medium | Very High |
| Operating System | Needs full OS install | Uses host OS/kernel features |
In reality we use containers and vm's together. Containers run inside of VM's for better security and isolation, especially in cloud and multi tenant environments.
---
@ -131,26 +150,17 @@ layout: center
## What is Docker?
- A tool to build and run containers
- Written in GO
- Uses Client/Server model with REST API (`docker cli` and `dockerd`)
- Eco system of tools (Compose, Swarm, etc)
- Public Image Registry (Dockerhub)
- Docker client typically runs on same machine as server but doesn't have to
---
transition: fade-out
layout: center
---
## What is Docker?
- A tool to build and run containers
- Containers are exclusive to Linux
- Docker engine runs containers using Linux features like:
- Uses Linux kernal features like:
- Namespaces
- cgroups
- Union file systems
- Container runs from an image layered with base image and application code
- Containers are just processes
---
transition: fade-out
layout: center
@ -182,24 +192,42 @@ layout: center
---
transition: fade-out
layout: center
layout: two-cols-header
---
## Bind/Volume Mounts
<div class="flex items-center flex-col">
<h1 class="ml-5">Bind/Volume Mounts</h1>
- 2 most common storage mechanisms
- Different use cases and security implications
<p>2 most common storage mechanisms<br />Different use cases and security implications</p>
</div>
::left::
## Bind Mounts
- Created/managed by user.
- Files from host mounted directly into container.
- Container processes can modify files on host system.
- Strongly tied to the host.
- Best for things like dev containers.
::right::
## Volume mounts
- Created/managed by Docker Daemon.
- Data is stored on host filesystem.
- Used for persistent data.
<!--
It is possible to modify the data directly via normal tools but unsupported and can cause unintended side-effects due to the overlayfs storage driver.
An example would be creating a postgres volume for persistent database storage.
-->
---
transition: fade-out
layout: center
image: 'https://unsplash.com/collections/oGE7TYSLt3I/software-development
equal: false
left: false
---
## Bind Mounts
- Mounting files/directories from the host machine directly into a container (merged overlayfs layer).
- Processes inside container can modify files on host system.
- Bind mounts are strongly tied to the host
- Best for things like dev containers where you need to mount source code into container and have hot reload, etc.
## Bind Mount Example
```bash
@ -253,20 +281,6 @@ $ docker volume inspect postgresData
- Docker creates a volume named postgresData and mounts that directory inside the container.
<!-- https://docs.docker.com/engine/storage/bind-mounts/ -->
---
transition: fade-out
layout: center
---
## Volume mounts
- Created and managed by the Docker Daemon
- Volume data is stored on host filesystem but managed by Docker.
- Used for persistent data.
<!--
It is possible to modify the data directly via normal tools but unsupported and can cause unintended side-effects due to the overlayfs storage driver.
An example would be creating a postgres volume for persistent database storage.
-->
---
transition: fade-out
@ -319,7 +333,7 @@ EXPOSE 5173
```bash
$ docker build -t react .
$ docker run --rm -P react
$ docker run --rm -p 5173:5173 react
```
<!--
Run docker image and demonstrate dev container functionality. Attach to the running container