🐳 Module Docker Avancé
Volumes · Réseaux · Images de production optimisées
Utilisez les flèches, cliquez ou glissez pour naviguer
1. Comprendre les types de montage Docker
Volumes nommés, bind mounts, tmpfs
2. Gérer la persistance des données
Volumes nommés pour les bases de données
3. Comprendre les réseaux Docker
Bridge, host, none — isolation et communication
4. Écrire des multi-stage builds
Séparer le build du runtime
5. Comparer les tailles d'images
Avant/après optimisation multi-stage
Rappel : données éphémères
Pourquoi les données disparaissent avec le conteneur
Les 3 types de montage
Volumes nommés, bind mounts, tmpfs
Réseaux Docker
Bridge (défaut), host, none — isolation et communication
Multi-stage builds
Build séparé du runtime pour des images minimales
Live coding
Transformer le Dockerfile en multi-stage build + comparer les tailles
Un conteneur qui s'arrête = des données perdues ?
Chaque conteneur démarre avec un filesystem vierge
# On crée un fichier dans le conteneur
docker run --name demo alpine sh -c "echo 'Données importantes' > /data/mon-fichier.txt"
# On supprime le conteneur
docker rm demo
# On relance un nouveau conteneur — les données ont disparu !
docker run alpine cat /data/mon-fichier.txt
cat: /data/mon-fichier.txt: No such file or directory
❌ Le filesystem du conteneur est éphémère
Quand le conteneur meurt, ses données meurent avec lui
🔄 Redémarrage du conteneur
PostgreSQL redémarre → toutes les tables sont vides
📦 Mise à jour de l'image
Nouveau déploiement → nouvelle image → anciennes données perdues
☠️ Crash du conteneur
Le conteneur plante → impossible de récupérer les logs ou les données
✅ La solution : monter un volume externe dans le conteneur
Les données vivent en dehors du conteneur — il ne fait que les utiliser
Volumes nommés · Bind mounts · tmpfs
📦 Volume nommé
Géré par Docker dans /var/lib/docker/volumes/
✅ Production (DB)
📁 Bind mount
Monte un dossier ou fichier de l'hôte
✅ Développement (hot-reload)
⚡ tmpfs
Stockage en mémoire RAM (temporaire)
✅ Secrets temporaires
# Syntaxe -v (ancienne) ou --mount (moderne)
docker run -v mon_volume:/chemin image
Le choix n°1 pour les données de production
docker volume create pgdata
docker run -d \
--name postgres \
-v pgdata:/var/lib/postgresql/data \
postgres:16
Géré par Docker
Création, sauvegarde, restauration via CLI
Persistant
Survit à la suppression du conteneur
Portable
Fonctionne sur tout OS (Linux, Mac, Windows)
💡 docker volume ls pour lister | docker volume inspect pgdata pour le chemin
Monte un dossier ou fichier de l'hôte dans le conteneur
docker run -d \
--name mon-app \
-v $(pwd):/app \
node:20 node server.js
✅ Avantages
⚠️ Attention
npm install dans le conteneur modifie node_modules sur l'hôteDonnées temporaires, volatiles, ultra-rapides
docker run -d \
--name app \
--tmpfs /tmp:noexec,nosuid,size=64m \
nginx
Cas d'usage
Limitations
💡 tmpfs = RAM = ultra-rapide, mais volatil. Parfait pour les caches Redis qui peuvent être reconstruits
| Critère | Volume nommé | Bind mount | tmpfs |
|---|---|---|---|
| Localisation | /var/lib/docker/volumes/ | Dossier hôte (choisi par l'utilisateur) | RAM (mémoire vive) |
| Persistance | ✅ Oui | ✅ Oui | ❌ Non |
| Performance | ⭐⭐⭐ | ⭐⭐⭐ (selon OS hôte) | ⭐⭐⭐⭐⭐ |
| Usage idéal | Production (DB, fichiers) | Développement (hot-reload) | Caches, secrets temporaires |
Isolation et communication entre conteneurs
🌉 bridge
Réseau privé par défaut
Les conteneurs communiquent entre eux via IP
docker run (par défaut)
🖥️ host
Partage le réseau de l'hôte
Pas d'isolation réseau — performances max
docker run --network host
🚫 none
Pas de réseau
Isolation totale — pas d'accès entrant/sortant
docker run --network none
🔗 Les conteneurs sur le même bridge peuvent communiquer
Ceux sur des réseaux différents sont isolés
# Créer un réseau personnalisé
docker network create --driver bridge mon-reseau
# Lancer des conteneurs sur ce réseau
docker run -d --name db --network mon-reseau postgres:16
docker run -d --name app --network mon-reseau -p 3000:3000 mon-app
# Le backend peut accéder à la base via "db:5432"
// Docker DNS résout automatiquement le nom du conteneur
DNS intégré
Les conteneurs se découvrent par leur nom
Isolation
Un réseau = un groupe isolé d'autres réseaux
⚠️ Les conteneurs sur des réseaux différents ne peuvent PAS communiquer — ils sont isolés par défaut !
Chaque docker compose up crée un réseau isolé pour la stack
🔗 Les services communiquent par nom de service
Dans votre code, utilisez db:5432 et non localhost:5432
# docker-compose.yml — réseau automatique
services:
backend:
build: .
ports: - "3000:3000"
db:
image: postgres:16
volumes: - pgdata:/var/lib/postgresql/data
# Dans le .env : DATABASE_URL=postgres://user:pass@db:5432/mydb
🖥️ host
Le conteneur partage la pile réseau de l'hôte
docker run --network host nginx
# accessible sur localhost:80 directement
⚠️ Pas d'isolation — conflit de ports possible
🚫 none
Aucun accès réseau (loopback uniquement)
docker run --network none alpine
# Pas d'internet, pas de communication
🔒 Idéal pour les traitements sensibles isolés
💡 99% du temps, vous utilisez le bridge par défaut ou un réseau personnalisé
Séparer le build du runtime
pour des images de production minimales
❌ Problème
Les outils de build (compilateurs, devDependencies) gonflent l'image de production
# Dockerfile simple — tout dans une image
FROM node:20
RUN npm install
RUN npm run build
# Image finale = 1 Go+ (TypeScript, node_modules, devDeps...)
✅ Solution
Compilez dans une image "builder", puis copiez SEULEMENT le résultat dans l'image finale
# Multi-stage — image finale = 150 Mo
# Stage 1 : builder avec outils de build
# Stage 2 : runtime minimal + artefact
📉 Objectif : réduire la taille de l'image de 1 Go → ~150 Mo
# Stage 1 : BUILD — on compile avec tous les outils
FROM node:20 AS builder
WORKDIR /app
COPY package.json package-lock.json .
RUN npm ci
COPY . .
RUN npm run build
# Stage 2 : RUNTIME — image légère, juste le nécessaire
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/dist /app
COPY --from=builder /app/node_modules /app/node_modules
EXPOSE 3000
CMD ["node", "server.js"]
📌 AS builder nomme le stage | --from=builder copie depuis un stage précédent
$ docker build -t mon-app:multi-stage .
✔ [builder 1/5] FROM node:20
✔ [builder 2/5] COPY package.json .
✔ [builder 3/5] RUN npm ci
✔ [builder 4/5] COPY . .
✔ [builder 5/5] RUN npm run build
► [runner 1/3] FROM node:20-alpine
► [runner 2/3] COPY --from=builder /app/dist /app
► [runner 3/3] COPY --from=builder /app/node_modules /app
✔ Image finale : 152 Mo
Les étapes clés :
L'instruction magique qui rend le multi-stage possible
COPY --from=builder /app/dist /app
# Copie depuis un stage nommé "builder"
# Source = /app/dist dans le builder
# Destination = /app dans le runner
✅ Ce qu'on copie
⚠️ Ce qu'on NE copie PAS
⚠️ Oublier de copier un fichier nécessaire → l'image finale plante au runtime !
Transformer le Dockerfile en multi-stage build
FROM node:20
WORKDIR /app
COPY package.json package-lock.json .
RUN npm ci
COPY . .
RUN npm run build
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "dist/server.js"]
❌ Problèmes :
# === STAGE 1 : BUILD ===
FROM node:20 AS builder
WORKDIR /app
COPY package.json package-lock.json .
RUN npm ci
COPY tsconfig.json .ts .
RUN npm run build
# === STAGE 2 : PRODUCTION ===
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package.json .
ENV NODE_ENV=production
EXPOSE 3000
USER node
CMD ["node", "dist/server.js"]
✅ Image finale basée sur node:20-alpine (~135 Mo) + uniquement dist/ et node_modules/
❌ Avant
1.02 Go
📦 node:20 (Debian full)
📦 node_modules (dev + prod)
📦 Fichiers sources .ts
📦 Caches npm
✅ Après
152 Mo
📦 node:20-alpine (minimal)
📦 node_modules (production only)
📦 dist/ (JS compilé)
📦 package.json
📉 Réduction de ~85 % de la taille de l'image !
Déploiements plus rapides, consommation disque réduite, surface d'attaque diminuée
$ docker build -t mon-app:optimise .
✔ CACHED [builder 1/5] FROM node:20
✔ CACHED [builder 2/5] COPY package.json .
► BUILD [builder 3/5] RUN npm ci (45s)
► BUILD [builder 4/5] COPY . .
► BUILD [builder 5/5] RUN npm run build (12s)
✔ CACHED [runner 1/3] FROM node:20-alpine
► BUILD [runner 2/3] COPY --from=builder /app/dist /app
► BUILD [runner 3/3] COPY --from=builder /app/node_modules /app
✔ Successfully built 8c3a1b2d9e0f
✔ Successfully tagged mon-app:optimise
💡 Les layers du builder sont en cache tant que les sources ne changent pas
$ docker images mon-app
REPOSITORY TAG SIZE
mon-app naif 1.02GB
mon-app optimise 152MB
1.02 Go — Image naïve
Contient le compilateur TypeScript, tous les .ts, les devDependencies, npm cache...
152 Mo — Image optimisée
Contient uniquement le JS compilé, les dépendances de production, et le runtime Alpine
🎯 docker images pour constater la différence 🎯
Les erreurs à éviter absolument
❌ Bind mount
-v /chemin/hote:/chemin/conteneur
Monte un dossier ou chemin existant de l'hôte
Dépend du système de fichiers de l'hôte
✅ Volume nommé
-v mon_volume:/chemin/conteneur
Géré par Docker dans /var/lib/docker/volumes/
Indépendant de l'OS hôte — portable
💡 Règle : chemin absolu /home/... = bind mount | nom simple pgdata = volume nommé
❌ Le piège
# docker-compose.yml
- ./app:/app
# Dans le conteneur : npm install
// Sur l'hôte : node_modules/ modifié !
npm install dans le conteneur modifie node_modules sur l'hôte
✅ La solution
# Solution 1 : volume anonyme pour node_modules
- /app/node_modules
# Solution 2 : .dockerignore + npm ci dans le Dockerfile
Empêcher l'écrasement du node_modules de l'hôte
💡 Astuce : ajoutez node_modules dans votre .gitignore pour éviter les commits accidentels
❌ L'erreur
# Stage runner
FROM node:20-alpine
COPY --from=builder /app/dist ./dist
CMD ["node", "dist/server.js"]
// 💥 Error: Cannot find module 'express'
❌ node_modules n'a pas été copié !
✅ La solution
# Stage runner
FROM node:20-alpine
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package.json .
CMD ["node", "dist/server.js"]
✅ Tous les fichiers nécessaires copiés
💡 Testez toujours l'image finale avec docker run mon-app avant de déployer
❌ L'erreur
# Conteneur A sur réseau "front"
docker run --network front nginx
# Conteneur B sur réseau "back"
docker run --network back postgres
// 💥 A ne peut pas joindre B !
Deux réseaux différents = pas de communication
✅ La solution
# Même réseau pour les deux
docker network create app-net
docker run --network app-net nginx
docker run --network app-net postgres
// ✅ Communication possible
Ou utilisez Docker Compose qui crée un réseau unique pour tous les services
📦 Volumes
Volumes nommés pour la production
Bind mounts pour le développement
tmpfs pour les données temporaires
🌉 Réseaux
Bridge par défaut — DNS par nom de service
Chaque compose crée son propre réseau
Des réseaux différents = isolation totale
🔨 Multi-stage
Build dans node:20, run dans alpine
COPY --from pour ne garder que le strict nécessaire
~85% de réduction de taille d'image
⚠️ Pièges
Bind mount ≠ volume nommé
Oublier de copier un fichier en multi-stage
Conteneurs isolés sur des réseaux différents
🐳 Des questions ?