4.4 — Conteneurisation (Docker)
🎯 Objectif : pouvoir conteneuriser n’importe quelle stack web et lancer un environnement complet (front + back + DB + cache) avec une seule commande.
À l'issue de cet axe, tu sauras :
- Distinguer image, conteneur, volume, réseau
- Écrire un Dockerfile multi-stage propre
- Composer une stack avec Docker Compose
- Optimiser la taille d'une image (de 1 Go à 200 Mo)
- Comprendre le rôle de Kubernetes (sans encore l'utiliser)
Débutant
Pourquoi Docker ?
Section intitulée « Pourquoi Docker ? »Avant Docker, tu disais : « ça marche sur ma machine ». Le serveur de prod te répondait : « mais pas sur la mienne ». Mille différences subtiles : version de Node, libssl, locale, packages OS, variables d’env.
Docker te donne un environnement reproductible : tu décris dans un fichier (Dockerfile) tout ce dont ton app a besoin pour tourner, et n’importe quelle machine avec Docker peut l’exécuter à l’identique.
Installer Docker
Section intitulée « Installer Docker »Avant d’aller plus loin, il faut Docker sur ta machine. Choisis ta plateforme :
Recommandé : Docker Desktop avec WSL2 (déjà installé si tu as suivi 1.1).
- Télécharge Docker Desktop for Windows : docker.com/products/docker-desktop
- Lance l’installeur, coche « Use WSL 2 instead of Hyper-V » quand on te le demande.
- Redémarre.
- Au premier lancement, Docker Desktop te demandera d’activer l’intégration WSL — accepte.
Pré-requis : la virtualisation doit être activée dans le BIOS (presque toujours le cas sur les machines récentes). Si Docker refuse de démarrer avec un message « Virtualization disabled », redémarre dans le BIOS et active Intel VT-x / AMD-V.
Recommandé : Docker Desktop.
brew install --cask docker# ou téléchargement direct sur docker.comLance Docker Desktop depuis Applications. Le 1er démarrage prend ~30 secondes.
Alternative plus légère : OrbStack (commercial, mais ~10× plus léger en RAM que Docker Desktop, démarre en 2 secondes).
Linux (Ubuntu / Debian)
Section intitulée « Linux (Ubuntu / Debian) »Docker Engine + Compose plugin sans Docker Desktop (plus léger en CLI pure) :
# Suivre la doc officielle :# https://docs.docker.com/engine/install/ubuntu/
# En résumé :sudo apt-get updatesudo apt-get install ca-certificates curlsudo install -m 0755 -d /etc/apt/keyringssudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.ascsudo chmod a+r /etc/apt/keyrings/docker.asc
echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get updatesudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Pour utiliser docker sans sudo :sudo usermod -aG docker $USER# Déconnecte/reconnecte ta sessionVérification
Section intitulée « Vérification »Quelle que soit la plateforme, vérifie que tout marche :
docker --version# Docker version 27.x.x
docker compose version# Docker Compose version v2.x.x
docker run hello-world# Hello from Docker!# (si tu vois ce message, c'est gagné)Les concepts en 3 minutes
Section intitulée « Les concepts en 3 minutes »flowchart LR
Dockerfile[Dockerfile<br/>recette] -->|docker build| Image
Image -->|docker run| Container1[Conteneur 1]
Image -->|docker run| Container2[Conteneur 2]
Container1 -->|écrit dans| Volume[Volume<br/>persistant]
Container1 <-->|réseau| Container2 Un modèle figé, en lecture seule. Contient OS minimal, ton code, ses dépendances.
docker pull node:22-alpine # télécharger une image officielledocker images # lister les images localesConteneur
Section intitulée « Conteneur »Une instance d’une image, en cours d’exécution (ou arrêtée).
docker run hello-worlddocker ps # conteneurs en coursdocker ps -a # tous, y compris arrêtésdocker stop <id>docker rm <id>Espace de stockage persistant (les conteneurs sont éphémères, les volumes survivent).
docker volume create mes-donnéesdocker run -v mes-données:/app/data ...Les conteneurs d’un même réseau peuvent se parler par nom.
docker network create mon-réseaudocker run --network=mon-réseau --name=db postgresdocker run --network=mon-réseau --name=api node:22-alpine# api peut joindre db via le hostname "db"Un Dockerfile minimal
Section intitulée « Un Dockerfile minimal »# Image de baseFROM node:22-alpine
# Dossier de travailWORKDIR /app
# Copier les fichiers de dépendances en premier (bénéficie du cache)COPY package.json package-lock.json ./
# InstallerRUN npm ci
# Copier le reste du codeCOPY . .
# BuildRUN npm run build
# Exposer le port (documentation, pas effectif)EXPOSE 3000
# Commande de démarrageCMD ["node", "dist/server.js"]# Construiredocker build -t mon-app:1.0 .
# Lancerdocker run -p 3000:3000 mon-app:1.0L’importance de l’ordre des couches
Section intitulée « L’importance de l’ordre des couches »Chaque instruction du Dockerfile crée une couche. Docker met en cache chaque couche. Si une couche change, toutes celles d’après sont reconstruites.
Mauvais ordre :
COPY . . # ← change tout le tempsRUN npm ci # ← invalidé à chaque commit !Bon ordre :
COPY package*.json ./ # ← rarement changeRUN npm ci # ← cache si package.json identiqueCOPY . . # ← copie le code aprèsRUN npm run buildRésultat : ton build passe de 2 minutes à 5 secondes quand tu modifies juste un fichier source.
Multi-stage builds — images minces
Section intitulée « Multi-stage builds — images minces »Le Dockerfile naïf produit une image énorme : Node, sources, devDependencies, fichiers temporaires.
Solution : multi-stage. Tu construis dans une image lourde, tu copies l’essentiel dans une image finale légère.
# Stage 1 — BuildFROM node:22-alpine AS builderWORKDIR /appCOPY package*.json ./RUN npm ciCOPY . .RUN npm run build
# Stage 2 — ProductionFROM node:22-alpine AS runtimeWORKDIR /appCOPY package*.json ./RUN npm ci --omit=devCOPY --from=builder /app/dist ./distEXPOSE 3000CMD ["node", "dist/server.js"]Bénéfice : la prod n’a ni TypeScript, ni Vite, ni node_modules de dev. Image passée de 1.2 Go à ~200 Mo.
.dockerignore
Section intitulée « .dockerignore »Pareil que .gitignore, mais pour Docker — évite de copier dans l’image ce qui ne sert pas (et accélère le build) :
node_modulesdist.git.env.env.**.logcoverage.DS_StoreREADME.md.vscodeDocker Compose — orchestrer plusieurs services
Section intitulée « Docker Compose — orchestrer plusieurs services »Pour une stack complète (front + back + DB), tu ne veux pas lancer 4 commandes docker run. Docker Compose orchestre tout en un fichier YAML.
# compose.yml (anciennement docker-compose.yml)services: db: image: postgres:16-alpine environment: POSTGRES_USER: app POSTGRES_PASSWORD: secret POSTGRES_DB: app_dev volumes: - db-data:/var/lib/postgresql/data ports: - "5432:5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U app"] interval: 5s timeout: 5s retries: 5
redis: image: redis:7-alpine ports: - "6379:6379"
api: build: context: ./api target: builder # multi-stage : on s'arrête au stage builder pour le dev environment: DATABASE_URL: postgres://app:secret@db:5432/app_dev REDIS_URL: redis://redis:6379 ports: - "3000:3000" volumes: - ./api:/app # bind mount pour hot-reload - /app/node_modules # ⚠ ne pas écraser node_modules du conteneur depends_on: db: condition: service_healthy command: npm run dev
web: build: ./web ports: - "5173:5173" volumes: - ./web:/app - /app/node_modules environment: VITE_API_URL: http://localhost:3000 command: npm run dev
volumes: db-data:docker compose up # tout démarredocker compose up -d # en arrière-plandocker compose logs -f api # suivre les logs d'un servicedocker compose down # tout arrêterdocker compose down -v # arrêter + supprimer les volumes (reset DB)Hot-reload en dev
Section intitulée « Hot-reload en dev »Pour Node.js (Express, Fastify…), utilise un wrapper qui surveille les fichiers :
# dans le Dockerfile devCMD ["npx", "tsx", "watch", "src/index.ts"]Ou avec nodemon, ts-node-dev, etc. Couplé au bind mount, tu modifies un fichier sur ta machine → le conteneur recharge automatiquement.
Pour Vite (front), pas besoin de plus — le HMR (Hot Module Replacement) marche tant que le port est exposé et que le bind mount est en place.
Dev Containers — coder dans un container, pas sur ton OS
Section intitulée « Dev Containers — coder dans un container, pas sur ton OS »Et si tu n’avais rien à installer sur ta machine — ni Node, ni Python, ni Postgres ? Tu ouvres VS Code, il détecte un fichier .devcontainer/devcontainer.json, build un conteneur, ouvre ton éditeur dedans. Toutes les extensions, tous les outils CLI vivent dans le conteneur.
C’est ce qu’on appelle Dev Containers — la spec officielle d’onboarding moderne, supportée par VS Code, Cursor, JetBrains, et GitHub Codespaces (qui te donne le conteneur dans le cloud).
Exemple minimal
Section intitulée « Exemple minimal »{ "name": "Taskly Dev", "image": "mcr.microsoft.com/devcontainers/typescript-node:24", "features": { "ghcr.io/devcontainers/features/docker-in-docker:2": {}, "ghcr.io/devcontainers/features/postgres:1": {} }, "forwardPorts": [3000, 5173, 5432], "postCreateCommand": "npm install", "customizations": { "vscode": { "extensions": [ "dbaeumer.vscode-eslint", "esbenp.prettier-vscode", "bradlc.vscode-tailwindcss" ] } }}VS Code (ou Cursor) détecte ce fichier, te propose « Reopen in Container », build, et tu es dans un environnement identique pour toute l’équipe — peu importe que tu sois sur Windows, macOS Intel, Mac M3 ou Linux.
Pourquoi c’est utile
Section intitulée « Pourquoi c’est utile »| Sans Dev Containers | Avec Dev Containers |
|---|---|
| « Mon Node est en 18, le tien en 24 » | Tout le monde a le même Node (celui défini dans l’image) |
| Onboarding nouveau dev = 2-3 h d’install | Onboarding = clone + Reopen in Container = 5 min |
| « Ça marche chez moi » | L’env est versionné dans le repo |
| Mac M3 incompatible avec un binaire natif | Le conteneur Linux résout |
Compose dev-container
Section intitulée « Compose dev-container »Pour les stacks complexes (api + web + db), tu peux utiliser docker-compose.yml directement :
{ "name": "Taskly Full Stack", "dockerComposeFile": "../compose.yml", "service": "api", "workspaceFolder": "/app", "shutdownAction": "stopCompose"}→ Tu réutilises le même compose.yml qu’en dev local. Pas de duplication.
GitHub Codespaces — le dev container dans le cloud
Section intitulée « GitHub Codespaces — le dev container dans le cloud »Avec un devcontainer.json dans le repo, tu peux ouvrir GitHub Codespaces (gratuit 60 h/mois) depuis n’importe quel navigateur — y compris un Chromebook ou un iPad. C’est le futur de l’onboarding pour les contributions open-source.
Compose profiles — un seul compose.yml pour dev et prod
Section intitulée « Compose profiles — un seul compose.yml pour dev et prod »Pour éviter d’avoir compose.dev.yml et compose.prod.yml séparés, utilise les profiles :
services: db: image: postgres:16-alpine # actif tout le temps (pas de profile)
pgadmin: image: dpage/pgadmin4 profiles: [dev] # actif seulement en dev ports: ["5050:80"]
api: build: target: ${BUILD_TARGET:-dev} # variable selon profile profiles: [dev, prod] # actif partout
monitoring: image: grafana/grafana profiles: [prod] # actif seulement en proddocker compose --profile dev up # api + db + pgadmindocker compose --profile prod up # api + db + monitoringdocker compose up # SEULS les services sans profile (= db)Plus de duplication. Tu choisis ton « mode » au lancement.
Variables d’environnement
Section intitulée « Variables d’environnement »3 façons de les passer :
# 1. CLI directdocker run -e DATABASE_URL=postgres://... mon-app
# 2. Fichier .envdocker run --env-file .env mon-app
# 3. Composeservices: api: env_file: .env environment: NODE_ENV: developmentRègle : ne jamais coder en dur des secrets dans le Dockerfile — ils sont visibles dans les couches de l’image.
Optimisations courantes
Section intitulée « Optimisations courantes »1. Choisir la bonne image de base
Section intitulée « 1. Choisir la bonne image de base »| Image | Taille typique | Usage |
|---|---|---|
node:22 (Debian) | ~1 Go | Beaucoup d’outils dispo, lourd |
node:22-slim | ~250 Mo | Bon compromis |
node:22-alpine | ~150 Mo | Ultra léger, mais musl libc parfois problématique |
gcr.io/distroless/nodejs22-debian12 | ~200 Mo | Pas de shell, pas de package manager — sécurité maximale |
2. Lancer en utilisateur non-root
Section intitulée « 2. Lancer en utilisateur non-root »# Ne JAMAIS tourner en root en prodFROM node:22-alpine AS runtimeRUN addgroup -g 1001 -S app && adduser -u 1001 -S app -G appUSER appWORKDIR /app# ...3. Activer le BuildKit
Section intitulée « 3. Activer le BuildKit »export DOCKER_BUILDKIT=1docker build .# ou plus simplement :docker buildx build .BuildKit parallélise les stages indépendants et cache plus efficacement.
Et Kubernetes ?
Section intitulée « Et Kubernetes ? »Kubernetes orchestre des centaines de conteneurs sur un cluster de machines. Concepts à connaître de loin :
| Objet K8s | Rôle |
|---|---|
| Pod | 1+ conteneurs colocalisés |
| Deployment | Définit un ensemble de pods avec scaling et rolling updates |
| Service | Adresse réseau stable pour atteindre des pods |
| Ingress | Routage HTTP externe (équivalent reverse proxy) |
| ConfigMap / Secret | Configuration et secrets |
Honnêtement : 95 % des projets web n’en ont pas besoin. PaaS comme Vercel, Render, Fly.io abstraient tout cela. Apprends K8s quand ton équipe en a besoin, pas avant.
Auto-évaluation
Section intitulée « Auto-évaluation »Pour aller plus loin
Section intitulée « Pour aller plus loin »- Docker — Get Started — docs.docker.com/get-started
- Best practices for writing Dockerfiles — docs.docker.com/build/building/best-practices
- Awesome Compose — github.com/docker/awesome-compose — exemples de stacks
- Snyk — 10 best practices — snyk.io/blog/10-docker-image-security-best-practices
🪤 Pièges réels rencontrés
Section intitulée « 🪤 Pièges réels rencontrés »Piège réel rencontré — better-sqlite3 ne compile pas sur Windows + Node 24 Build / Native
🩹 Symptôme
npm error gyp ERR! stack ... node-gyp rebuildbetter-sqlite3 ... not oknode -v v24.14.1🔍 Cause
better-sqlite3 est un module natif C++ : il doit être compilé via node-gyp. Sous Windows, ça exige les Visual C++ Build Tools (~ 5 GB). Sous Node 24 (très récent), les binaires prébuilds ne sont pas toujours encore publiés au moment de la sortie de Node, ce qui force la compilation locale — laquelle échoue si l’environnement de build n’est pas configuré.
🩺 Fix
Trois options par ordre de préférence :
- Migrer vers
@libsql/client— fork Turso de SQLite, binaires prébuilds pour toutes plateformes, pas de compilation. Drizzle a un driverdrizzle-orm/libsqlquasi drop-in. C’est ce que ce guide utilise dans l’exercicetaskly-api. - Installer les Build Tools Windows :
npm install --global windows-build-toolsou via Visual Studio Installer → « C++ Build Tools ». - Downgrader Node à 22 LTS où les prébuilds existent.
🧠 Leçon
Pour un projet pédagogique ou multi-plateforme, éviter les modules natifs quand un équivalent pure-JS / WASM existe. La friction d’installation tue les contributions. @libsql/client, @noble/hashes, @node-rs/argon2 (lui-même prébuild) sont des bons défauts 2026.
🔍 Tous les pièges du guide sont sur la page /pieges/ — searchable par symptôme.
Suite : 4.5 — Productivité — dotfiles, terminal moderne, ripgrep, fzf, tmux.