Docker Cheatsheet Docker 26.x

Qu'est-ce que Docker ?

Docker est une plateforme de conteneurisation : elle permet d'empaqueter une application et toutes ses dépendances dans une unité isolée et portable appelée container. Contrairement à une VM, un container partage le noyau du système hôte — il démarre en millisecondes et consomme très peu de ressources.

ARCHITECTURE DOCKER 👨‍💻 Développeur docker build / run DOCKER DAEMON Images Containers Volumes Networks Kernel Linux (partagé) pull/push 🗄️ Registry Docker Hub / ECR / GCR CONTAINERS EN COURS app db VM vs CONTAINER 🖥 VM App OS complet (~GBs) Hyperviseur Hardware Démarrage: minutes 🐳 Container App Libs (~MBs) Kernel partagé (host OS) Démarrage: ~100ms
En résumé : une Image est un template en lecture seule (comme une recette). Un Container est une instance en cours d'exécution de cette image (le plat cuisiné). Plusieurs containers peuvent tourner depuis la même image.

Docker repose sur 3 primitives Linux : namespaces (isolation des processus, réseau, fichiers), cgroups (limitation CPU/RAM) et Union FS (couches de système de fichiers superposées). Le résultat : isolation quasi-totale sans overhead de virtualisation.

Démarrage rapide — 5 minutes

1
Installer Docker
Télécharger Docker Desktop (Mac/Windows) ou installer le moteur sur Linux : curl -fsSL https://get.docker.com | sh. Vérifier : docker version
2
Lancer votre premier container
Pas besoin de rien installer — Docker télécharge l'image automatiquement depuis Docker Hub.
Terminal
# Lancer un serveur web Nginx en 1 commande
docker run -d -p 8080:80 --name mon-nginx nginx

# Ouvrir http://localhost:8080 dans votre navigateur → page Nginx !

# Voir les containers en cours
docker ps

# Arrêter et supprimer
docker stop mon-nginx
docker rm mon-nginx
3
Créer votre première image
Créer un fichier Dockerfile à la racine de votre projet.
Dockerfile (Node.js basique)
# Partir d'une image de base officielle
FROM node:20-alpine

# Définir le répertoire de travail dans le container
WORKDIR /app

# Copier les fichiers de dépendances
COPY package*.json ./

# Installer les dépendances
RUN npm install

# Copier le reste du code source
COPY . .

# Exposer le port sur lequel l'app tourne
EXPOSE 3000

# Commande lancée au démarrage du container
CMD ["node", "server.js"]
4
Builder et lancer
Les deux commandes essentielles : build puis run.
Terminal
# Construire l'image (. = répertoire courant)
docker build -t mon-app:1.0 .

# Lancer le container depuis l'image
docker run -d -p 3000:3000 --name mon-app mon-app:1.0

# Voir les logs
docker logs -f mon-app
5
Créer un .dockerignore
Éviter de copier node_modules, .env et autres fichiers inutiles dans l'image.
.dockerignore
node_modules
.env
.env.*
.git
.gitignore
*.log
dist
coverage
README.md
Conseil : Toujours créer un .dockerignore avant de builder. Sans lui, Docker copie tout le répertoire — y compris node_modules (des centaines de MB) — ce qui ralentit massivement le build et gonfle inutilement l'image.
1

Concepts fondamentaux

CYCLE DE VIE D'UNE IMAGE → CONTAINER Dockerfile Instructions de build build Image Template read-only run Container Instance en cours d'exécution push pull Registry Docker Hub, ECR… Autres ressources 🗂 Volume 🌐 Network 🔌 Bind Mount
ConceptDéfinitionAnalogieCaractéristique clé
ImageTemplate immutable composé de couches (layers) superposéesRecette de cuisinePartagée entre containers, stockée en cache local
ContainerInstance en cours d'exécution d'une image, avec sa propre couche d'écriturePlat cuisinéÉphémère par défaut (données perdues à l'arrêt)
DockerfileFichier texte décrivant les étapes pour construire une imageLa recette écriteVersionnable, reproductible
RegistryServeur de stockage et distribution d'images (Docker Hub, ECR, GCR, Harbor)Bibliothèque de recettesPublic ou privé
VolumeStockage persistant géré par Docker, monté dans un containerDisque dur externeSurvit à la suppression du container
NetworkRéseau virtuel isolé permettant la communication entre containersRéseau local privéDNS automatique entre containers sur le même réseau
LayerCouche de système de fichiers issue d'une instruction Dockerfile (RUN, COPY…)Couche de peintureMise en cache — si non modifiée, réutilisée sans rebuildé
TagÉtiquette versionnant une image (ex: nginx:1.25-alpine)Numéro de versionlatest = tag par défaut (attention : pas toujours le plus récent)
Piège courant : un container arrêté (docker stop) n'est pas supprimé. Ses données (sans volume) et sa couche d'écriture sont encore présentes sur disque. Utiliser docker rm pour supprimer, ou docker run --rm pour supprimer automatiquement à l'arrêt.
2

CLI — Gestion des images

CommandeDescriptionExemple
docker imagesLister les images localesdocker images
docker pullTélécharger une image depuis un registrydocker pull nginx:1.25-alpine
docker buildConstruire une image depuis un Dockerfiledocker build -t monapp:1.0 .
docker pushEnvoyer une image vers un registrydocker push monuser/monapp:1.0
docker tagCréer un alias (tag) pour une imagedocker tag monapp:1.0 monapp:latest
docker rmiSupprimer une image localedocker rmi nginx:1.25-alpine
docker image pruneSupprimer toutes les images non utiliséesdocker image prune -a
docker inspectAfficher les métadonnées complètes d'une imagedocker inspect nginx
docker historyAfficher les couches (layers) d'une imagedocker history monapp:1.0
docker save / loadExporter/importer une image en .tar (sans registry)docker save monapp:1.0 | gzip > monapp.tar.gz
Commandes de build avancées
# Build avec BuildKit (plus rapide, activé par défaut depuis Docker 23)
docker build -t monapp:1.0 .

# Build avec cache désactivé (forcer la reconstruction complète)
docker build --no-cache -t monapp:1.0 .

# Build depuis un Dockerfile non standard
docker build -f docker/Dockerfile.prod -t monapp:prod .

# Build avec build arguments (variables passées au build)
docker build --build-arg NODE_ENV=production -t monapp:prod .

# Build multi-platform (ex: x86 + ARM pour Apple Silicon)
docker buildx build --platform linux/amd64,linux/arm64 -t monapp:1.0 --push .

# Inspecter la taille de chaque layer
docker history --no-trunc monapp:1.0
Convention de nommage : registre/utilisateur/image:tag — ex: docker.io/library/nginx:1.25-alpine. Pour Docker Hub, le préfixe docker.io/library/ est implicite. Pour un registry privé : registry.company.com/team/app:v1.2.3.
3

CLI — Gestion des containers

CommandeDescriptionOptions utiles
docker runCréer et démarrer un container-d (détaché), -p (ports), -v (volumes), -e (env), --name
docker psLister les containers actifs-a (tous), -q (IDs seulement)
docker start / stopDémarrer / arrêter un container existantdocker stop -t 30 monapp (timeout 30s)
docker restartRedémarrer un containerdocker restart monapp
docker rmSupprimer un container arrêté-f (forcer si actif)
docker execExécuter une commande dans un container actif-it (interactif + TTY)
docker logsAfficher les logs d'un container-f (follow), --tail 50, --since 1h
docker cpCopier des fichiers container ↔ hôtedocker cp monapp:/app/config.json ./
docker statsMonitoring en temps réel (CPU, RAM, réseau)docker stats --no-stream
docker topAfficher les processus dans un containerdocker top monapp
docker commitCréer une image depuis un container modifié (déconseillé en prod)docker commit monapp monapp:debug
docker container pruneSupprimer tous les containers arrêtésdocker container prune -f
Options docker run — référence complète
docker run \
  -d \                          # mode détaché (background)
  --name mon-container \        # nom du container
  -p 8080:80 \                  # port hôte:port container
  -p 443:443 \                  # plusieurs ports possible
  -v mon-volume:/data \         # volume nommé
  -v $(pwd)/config:/app/config \# bind mount (chemin hôte:container)
  -e DATABASE_URL=postgres://…\ # variable d'environnement
  --env-file .env \             # charger un fichier .env
  --network mon-reseau \        # réseau Docker
  --restart unless-stopped \    # politique de redémarrage
  --memory 512m \               # limite RAM
  --cpus 1.5 \                  # limite CPU
  --rm \                        # supprimer le container à l'arrêt
  --user 1001:1001 \            # utilisateur non-root
  --read-only \                 # filesystem en lecture seule
  monimage:tag                  # image à utiliser

# Entrer dans un container en cours
docker exec -it mon-container bash
docker exec -it mon-container sh   # si bash absent (alpine)

# Container interactif jetable
docker run --rm -it ubuntu:22.04 bash

# Suivre les logs en live
docker logs -f --tail 100 mon-container
Politiques de redémarrage : no (défaut), on-failure[:n] (si exit code non nul, n fois max), always (toujours, même après reboot), unless-stopped (comme always, sauf si arrêté manuellement). En production, préférer unless-stopped.
4

Écrire un Dockerfile

InstructionRôleExempleNotes
FROMImage de base (obligatoire, en premier)FROM node:20-alpineUtiliser des images officielles slim/alpine
WORKDIRRépertoire de travail courantWORKDIR /appCrée le dossier si inexistant
COPYCopier des fichiers hôte → imageCOPY package*.json ./Respecte .dockerignore
ADDComme COPY mais supporte URL et .tarADD app.tar.gz /appPréférer COPY sauf besoin spécifique
RUNExécuter une commande pendant le buildRUN npm ci --only=productionChaque RUN = nouveau layer
ENVVariable d'environnement persistanteENV NODE_ENV=productionVisible dans le container au runtime
ARGVariable disponible uniquement au buildARG APP_VERSION=1.0Non présente dans l'image finale
EXPOSEDocumenter le port exposé (indicatif)EXPOSE 3000Ne publie pas le port (--publish requis)
CMDCommande par défaut au démarrageCMD ["node", "server.js"]Surchargeable au docker run
ENTRYPOINTCommande fixe + CMD comme argumentsENTRYPOINT ["node"]Plus difficile à surcharger
VOLUMEDéclarer un point de montageVOLUME ["/data"]Crée un volume anonyme si non spécifié
USERUtilisateur pour les instructions suivantesUSER nodeSécurité : éviter root
HEALTHCHECKVérification de santé du containerHEALTHCHECK CMD curl -f http://localhost/Statuts: starting/healthy/unhealthy
LABELMétadonnées de l'imageLABEL version="1.0" maintainer="team"OCI image spec
SHELLShell utilisé pour les RUN suivantsSHELL ["/bin/bash", "-c"]Défaut: /bin/sh -c
ONBUILDInstruction déclenchée si l'image est utilisée comme baseONBUILD COPY . /appUtile pour images de base d'équipe
Bonnes pratiques — optimiser les layers et la taille
Dockerfile optimisé — Node.js production
# ✅ BIEN : image légère
FROM node:20-alpine

# Créer un utilisateur non-root dès le début
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

WORKDIR /app

# Copier package.json SÉPARÉMENT → meilleur cache
# Si le code change mais pas les deps, cette couche reste en cache
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force

# Copier le reste du code
COPY --chown=appuser:appgroup . .

# Passer à l'utilisateur non-root
USER appuser

EXPOSE 3000

# Healthcheck intégré
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
  CMD wget -qO- http://localhost:3000/health || exit 1

# Préférer la forme exec (tableau JSON) à la forme shell
CMD ["node", "server.js"]
❌ Antipatterns courants
# ❌ Plusieurs RUN séparés = plusieurs layers inutiles
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y git

# ✅ Chaîner avec && et nettoyer dans le même RUN
RUN apt-get update && apt-get install -y \
    curl \
    git \
  && rm -rf /var/lib/apt/lists/*

# ❌ COPY tout en premier (invalide le cache à chaque modification de code)
COPY . .
RUN npm ci

# ✅ COPY package.json en premier, code source ensuite
COPY package*.json ./
RUN npm ci
COPY . .

# ❌ Laisser des outils de build dans l'image finale
RUN apt-get install -y build-essential && make && # outils présents dans l'image !
# ✅ Utiliser le multi-stage build (section 5)
CMD vs ENTRYPOINT : CMD définit la commande par défaut, facilement surchargeable avec docker run image ma-commande. ENTRYPOINT est fixe, CMD devient ses arguments. Combinaison classique : ENTRYPOINT ["node"] + CMD ["server.js"]docker run image autre.js lance node autre.js.
5

Multi-stage builds

Le multi-stage build permet d'utiliser plusieurs FROM dans un seul Dockerfile. On compile dans une image lourde, puis on copie uniquement les artefacts produits dans une image finale légère. Résultat : des images de production beaucoup plus petites (souvent 10x moins lourdes) sans outils de build.

MULTI-STAGE BUILD STAGE 1 — builder FROM node:20 (600 MB) npm install + npm run build → dist/ généré (artefact) ⚠ Taille totale: ~800 MB COPY --from=builder STAGE 2 — production FROM node:20-alpine (60 MB) COPY --from=builder /app/dist ./dist CMD ["node", "dist/server.js"] ✅ Taille finale: ~120 MB AVANTAGES ✓ Image ~7x plus petite ✓ Aucun outil de build exposé ✓ Surface d'attaque réduite ✓ 1 seul Dockerfile
Multi-stage — Application React + Node.js
# ─── Stage 1: Dépendances ────────────────────────────
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

# ─── Stage 2: Build ──────────────────────────────────
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

# ─── Stage 3: Tests (optionnel, ciblable séparément) ─
FROM builder AS test
RUN npm run test -- --coverage

# ─── Stage 4: Production ─────────────────────────────
FROM node:20-alpine AS production
WORKDIR /app
ENV NODE_ENV=production

RUN addgroup -S appgroup && adduser -S appuser -G appgroup

# Copier uniquement le build final + deps de prod
COPY --from=builder --chown=appuser:appgroup /app/dist ./dist
COPY --from=deps /app/node_modules ./node_modules
COPY --chown=appuser:appgroup package.json ./

USER appuser
EXPOSE 3000
CMD ["node", "dist/server.js"]
Cibler un stage spécifique
# Builder jusqu'au stage "production" uniquement
docker build --target production -t monapp:prod .

# Builder jusqu'au stage "test" (utile en CI)
docker build --target test -t monapp:test .

# Le stage "test" peut être lancé pour valider
docker run --rm monapp:test
6

Instructions avancées du Dockerfile

ARG vs ENV
Dockerfile — ARG et ENV
# ARG : disponible uniquement pendant le build
ARG APP_VERSION=1.0.0
ARG BUILD_DATE

# ENV : persisté dans l'image, accessible au runtime
ENV NODE_ENV=production \
    PORT=3000 \
    APP_VERSION=${APP_VERSION}

LABEL org.opencontainers.image.version=${APP_VERSION} \
      org.opencontainers.image.created=${BUILD_DATE}

# Passer au build
# docker build --build-arg APP_VERSION=2.1.0 --build-arg BUILD_DATE=$(date -I) .
HEALTHCHECK
Dockerfile — Healthcheck
# HTTP healthcheck (wget disponible sur alpine)
HEALTHCHECK \
  --interval=30s \    # fréquence de vérification
  --timeout=5s \      # délai max par check
  --start-period=30s \# délai avant 1er check (app en démarrage)
  --retries=3 \       # nb d'échecs avant unhealthy
  CMD wget -qO- http://localhost:3000/health || exit 1

# Voir l'état de santé
# docker inspect --format='{{.State.Health.Status}}' monapp
# docker inspect --format='{{json .State.Health}}' monapp | jq
ENTRYPOINT avec script d'initialisation
docker-entrypoint.sh + Dockerfile
#!/bin/sh   # docker-entrypoint.sh
set -e

# Attendre que la base de données soit prête
until nc -z ${DB_HOST:-db} ${DB_PORT:-5432}; do
  echo "Waiting for PostgreSQL..."
  sleep 2
done

# Lancer les migrations
node dist/migrate.js

# Lancer l'application avec les arguments passés à docker run
exec "$@"
Dockerfile — utiliser le script
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["node", "dist/server.js"]
7

Volumes & persistance des données

3 TYPES DE MONTAGE VOLUME NOMMÉ -v mon-volume:/data 🗂 Docker gère l'emplacement /var/lib/docker/volumes/ mon-volume/_data ✓ Persiste après rm container ✓ Partageable entre containers BIND MOUNT -v /chemin/hote:/chemin/container 📁 Chemin hôte explicite Dossier hôte synchronisé en temps réel ✓ Idéal pour le développement ⚠ Dépend du chemin hôte TMPFS MOUNT --tmpfs /tmp ⚡ Stockage en mémoire RAM Non persisté sur disque Perdu à l'arrêt du container ✓ Très rapide (RAM) ✓ Pour données sensibles temporaires
Gestion des volumes
# ─── Volumes nommés ──────────────────────────────────
docker volume create mon-volume
docker volume ls
docker volume inspect mon-volume
docker volume rm mon-volume
docker volume prune         # supprimer volumes non utilisés

# Monter un volume dans un container
docker run -d -v mon-volume:/data postgres:16-alpine

# ─── Bind mount (développement) ──────────────────────
# Code source monté en live → hot reload
docker run -d \
  -v $(pwd)/src:/app/src \
  -v $(pwd)/public:/app/public \
  -p 3000:3000 monapp:dev

# ─── Partager un volume entre containers ─────────────
docker run -d --name db -v pgdata:/var/lib/postgresql/data postgres:16
docker run -d --name app -v pgdata:/backups:ro backup-tool

# ─── Sauvegarder / restaurer un volume ───────────────
# Backup
docker run --rm -v mon-volume:/data -v $(pwd):/backup alpine \
  tar czf /backup/mon-volume.tar.gz -C /data .

# Restore
docker run --rm -v mon-volume:/data -v $(pwd):/backup alpine \
  tar xzf /backup/mon-volume.tar.gz -C /data
En développement : utiliser des bind mounts (-v $(pwd):/app) pour voir les modifications en temps réel sans rebuilder. En production : utiliser des volumes nommés gérés par Docker — plus portables et indépendants du système de fichiers hôte.
8

Réseaux Docker

DriverDescriptionCas d'usage
bridgeRéseau privé isolé (défaut). Containers sur le même bridge peuvent communiquer via DNSContainers sur un même hôte
hostPartage le réseau de l'hôte directement (pas d'isolation réseau)Haute performance, monitoring
noneAucune interface réseau (totalement isolé)Jobs batch sans besoin réseau
overlayRéseau multi-hôtes pour Docker Swarm / KubernetesClusters distribués
macvlanAttribue une MAC address dédiée, le container semble être sur le LANIntégration réseau physique
Gestion des réseaux
# Créer un réseau bridge personnalisé
docker network create mon-reseau
docker network create --driver bridge --subnet 172.20.0.0/16 mon-reseau

# Lister les réseaux
docker network ls
docker network inspect mon-reseau

# Connecter des containers au même réseau
docker run -d --name db --network mon-reseau postgres:16-alpine
docker run -d --name app --network mon-reseau -p 3000:3000 monapp

# Depuis "app", accéder à "db" par son nom (DNS automatique)
# DATABASE_URL=postgres://user:pass@db:5432/mydb  ← "db" résolu automatiquement !

# Connecter un container existant à un réseau
docker network connect mon-reseau mon-container
docker network disconnect mon-reseau mon-container

# Supprimer un réseau
docker network rm mon-reseau
docker network prune   # supprimer réseaux non utilisés
DNS automatique : sur un réseau bridge personnalisé (pas le réseau bridge par défaut), les containers se trouvent par leur nom. Si votre container s'appelle db, l'autre container peut l'appeler avec l'hostname db. C'est pour ça que dans Compose, le service name sert directement d'hostname.
Réseau bridge par défaut (legacy) : le réseau bridge créé par défaut par Docker ne supporte pas la résolution DNS par nom de container. Toujours créer un réseau bridge personnalisé (docker network create) pour la communication inter-containers.
9

Docker Compose — Bases

Docker Compose permet de décrire et lancer une stack multi-containers avec un seul fichier docker-compose.yml. Une seule commande remplace une dizaine de docker run avec tous leurs paramètres.

docker-compose.yml — Application Node.js + PostgreSQL + Redis
# Version de la syntaxe Compose
version: '3.9'

services:
  # ─── Application Node.js ─────────────────────────
  app:
    build:
      context: .
      dockerfile: Dockerfile
      target: production          # stage multi-stage
    image: monapp:latest
    container_name: monapp
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      NODE_ENV: production
      DATABASE_URL: postgres://user:password@db:5432/mydb
      REDIS_URL: redis://cache:6379
    env_file:
      - .env                      # variables depuis fichier
    depends_on:
      db:
        condition: service_healthy # attendre que db soit healthy
      cache:
        condition: service_started
    networks:
      - backend
    volumes:
      - ./uploads:/app/uploads
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
      interval: 30s
      timeout: 5s
      retries: 3

  # ─── Base de données PostgreSQL ───────────────────
  db:
    image: postgres:16-alpine
    container_name: postgres
    restart: unless-stopped
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
      POSTGRES_DB: mydb
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - backend
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user -d mydb"]
      interval: 10s
      timeout: 5s
      retries: 5

  # ─── Cache Redis ──────────────────────────────────
  cache:
    image: redis:7-alpine
    container_name: redis
    restart: unless-stopped
    command: redis-server --appendonly yes --maxmemory 256mb
    volumes:
      - redisdata:/data
    networks:
      - backend

networks:
  backend:
    driver: bridge

volumes:
  pgdata:
  redisdata:
CommandeDescription
docker compose up -dDémarrer tous les services en background
docker compose up --buildRebuilder les images avant de démarrer
docker compose downArrêter et supprimer containers + réseau
docker compose down -vIdem + supprimer les volumes
docker compose psLister les services et leur état
docker compose logs -fSuivre les logs de tous les services
docker compose logs -f appLogs d'un seul service
docker compose exec app shShell dans un service actif
docker compose restart appRedémarrer un service
docker compose pullMettre à jour toutes les images
docker compose configValider et afficher la config résolue
docker compose run --rm app node migrate.jsLancer une commande one-shot
Note : depuis Docker Compose v2 (intégré dans Docker Desktop), la commande est docker compose (avec espace) et non docker-compose (avec tiret). Les deux fonctionnent mais docker compose est la version moderne recommandée.
10

Docker Compose — Avancé

Fichiers multiples — override par environnement
Structure de fichiers Compose
# docker-compose.yml          ← base commune (partagée)
# docker-compose.override.yml ← dev (chargé automatiquement)
# docker-compose.prod.yml     ← production (spécifié manuellement)

# Développement (override automatique)
docker compose up

# Production
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

# CI/CD
docker compose -f docker-compose.yml -f docker-compose.ci.yml up --build --exit-code-from tests
docker-compose.override.yml (développement)
version: '3.9'
services:
  app:
    build:
      target: development        # stage dev du multi-stage
    volumes:
      - .:/app                   # bind mount pour hot reload
      - /app/node_modules        # exclure node_modules du bind
    environment:
      NODE_ENV: development
      DEBUG: "app:*"
    ports:
      - "9229:9229"               # port debug Node.js
    command: npm run dev         # surcharger CMD du Dockerfile

  db:
    ports:
      - "5432:5432"               # exposer PG en dev seulement
Profiles — services optionnels
docker-compose.yml — profiles
services:
  app:
    image: monapp
    # pas de profile = toujours démarré

  adminer:
    image: adminer
    profiles: ["tools"]           # démarré uniquement si profil activé
    ports:
      - "8080:8080"

  mailhog:
    image: mailhog/mailhog
    profiles: ["tools", "dev"]
    ports:
      - "1025:1025"
      - "8025:8025"

# Activer un profile
# docker compose --profile tools up -d
# COMPOSE_PROFILES=tools docker compose up -d
Extension fields & ancres YAML
Réutilisation YAML avec x-
# Définir des blocs réutilisables
x-common-env: &common-env
  NODE_ENV: production
  TZ: Europe/Paris
  LOG_LEVEL: info

x-common-labels: &common-labels
  labels:
    app: monapp
    env: production

services:
  api:
    image: monapp-api
    environment:
      <<: *common-env
      PORT: 3001
    <<: *common-labels

  worker:
    image: monapp-worker
    environment:
      <<: *common-env
      WORKER_CONCURRENCY: 4
    <<: *common-labels
Scale — plusieurs répliques d'un service
Scaling
# Scaler un service à 3 instances
docker compose up -d --scale worker=3

# Dans le compose, déclarer le deploy (pour Swarm aussi)
services:
  worker:
    image: monapp-worker
    deploy:
      replicas: 3
      restart_policy:
        condition: on-failure
        max_attempts: 3
      resources:
        limits:
          cpus: '0.5'
          memory: 256M
11

Registry & gestion des images

RegistryURLUtilisation
Docker Hubdocker.ioRegistry public officiel, images officielles
GitHub Container Registryghcr.ioIntégré à GitHub Actions, gratuit pour public
AWS ECR*.dkr.ecr.*.amazonaws.comIntégré à AWS ECS/EKS
Google Artifact Registry*.pkg.devIntégré à GCP Cloud Run/GKE
Azure Container Registry*.azurecr.ioIntégré à Azure AKS
HarborSelf-hostedRegistry privé open-source, scan de vulnérabilités
GitLab Registryregistry.gitlab.comIntégré à GitLab CI/CD
Workflow complet push / pull
# ─── Docker Hub ──────────────────────────────────────
docker login                              # login interactif
docker login -u $USER -p $TOKEN           # login non-interactif (CI)

docker tag monapp:1.0 monuser/monapp:1.0
docker push monuser/monapp:1.0
docker pull monuser/monapp:1.0

# ─── GitHub Container Registry ───────────────────────
echo $GITHUB_TOKEN | docker login ghcr.io -u $GITHUB_USER --password-stdin
docker tag monapp:1.0 ghcr.io/monorg/monapp:1.0
docker push ghcr.io/monorg/monapp:1.0

# ─── AWS ECR ─────────────────────────────────────────
aws ecr get-login-password --region eu-west-1 | \
  docker login --username AWS --password-stdin 123456789.dkr.ecr.eu-west-1.amazonaws.com
docker tag monapp:1.0 123456789.dkr.ecr.eu-west-1.amazonaws.com/monapp:1.0
docker push 123456789.dkr.ecr.eu-west-1.amazonaws.com/monapp:1.0

# ─── Registry privé local (développement/CI) ─────────
docker run -d -p 5000:5000 --name registry registry:2
docker tag monapp:1.0 localhost:5000/monapp:1.0
docker push localhost:5000/monapp:1.0
docker pull localhost:5000/monapp:1.0
Bonnes pratiques de tagging
Stratégie de tags
# ✅ Tags recommandés par build
IMAGE=ghcr.io/monorg/monapp
COMMIT=$(git rev-parse --short HEAD)
VERSION=$(git describe --tags --abbrev=0 2>/dev/null || echo "dev")

docker build \
  -t $IMAGE:$VERSION \      # ex: v1.2.3
  -t $IMAGE:$COMMIT \       # ex: a1b2c3d (traçabilité)
  -t $IMAGE:latest \        # toujours pointer vers le dernier
  .

# ✅ Immutable tags en production : utiliser le sha256 digest
docker pull monapp@sha256:abc123def456...
# Garantit qu'on lance exactement cette image, même si le tag change
12

Variables d'environnement & secrets

MéthodeCommande / ConfigSécuritéUsage
-e KEY=VALdocker run -e DB_PASS=secret⚠ Visible dans docker inspectDev / valeurs non sensibles
--env-filedocker run --env-file .env⚠ Fichier .env en clair sur disqueDev local
env_file Composeenv_file: [.env]⚠ Même risque que --env-fileDev / Compose local
Docker Secrets (Swarm)docker secret create✅ Chiffré, monté en tmpfsProduction Swarm
Secrets Compose (fichier)secrets: dans compose✅ Monté en /run/secrets/Dev avec secrets
Variables CI/CDGitLab/GitHub secrets✅ Masquées dans les logsCI/CD pipelines
Vault / AWS Secrets ManagerInjectés au runtime✅✅ Rotation automatiqueProduction enterprise
Secrets dans Docker Compose
version: '3.9'
services:
  app:
    image: monapp
    secrets:
      - db_password
      - api_key
    environment:
      # L'app lit /run/secrets/db_password au runtime
      DB_PASSWORD_FILE: /run/secrets/db_password

  db:
    image: postgres:16-alpine
    secrets:
      - db_password
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt   # fichier local (dev)
  api_key:
    external: true                     # secret Swarm externe (prod)
Lire un secret depuis l'application (Node.js)
// Lire le secret depuis le fichier monté par Docker
const readSecret = (name) => {
  const path = `/run/secrets/${name}`;
  try {
    return require('fs').readFileSync(path, 'utf8').trim();
  } catch {
    // Fallback sur variable d'environnement (dev sans secret)
    return process.env[name.toUpperCase()] || null;
  }
};

const dbPassword = readSecret('db_password');
Ne jamais faire : COPY .env /app/ dans un Dockerfile, ni RUN export SECRET=valeur. Ces valeurs seraient gravées dans les layers de l'image et visibles par quiconque fait docker history ou docker inspect. Utiliser des build args uniquement pour des valeurs non sensibles.
13

Sécurité & limites de ressources

Limites CPU et mémoire
Limites de ressources
# Limites via docker run
docker run \
  --memory 512m \          # max RAM (aussi: 1g, 256m)
  --memory-swap 512m \     # =memory → désactive le swap
  --cpus 1.5 \             # max 1.5 cores
  --cpu-shares 512 \       # priorité relative (défaut 1024)
  monapp

# Vérifier en live
docker stats monapp

# Dans Compose
services:
  app:
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 128M
Checklist sécurité
RègleCommentPourquoi
Utilisateur non-rootUSER node dans Dockerfile ou --user 1001Limite l'impact d'une compromission
Image minimalePréférer -alpine, -slim, scratch, distrolessMoins de packages = moins de CVE
Scan d'imagedocker scout cves monapp:1.0 ou TrivyDétecter les vulnérabilités connues
Filesystem read-only--read-only + tmpfs pour /tmpEmpêche l'écriture non autorisée
Capabilities réduites--cap-drop ALL --cap-add NET_BIND_SERVICEPrincipe du moindre privilège
No new privileges--security-opt no-new-privilegesEmpêche l'escalade de privilèges
PID limité--pids-limit 200Protection contre fork bombs
Réseau isoléRéseaux Compose séparés par coucheDB non exposée au monde extérieur
Tags fixesimage: postgres:16.2 pas postgres:latestReproductibilité, pas de surprise au redéploiement
Secret sécurisésDocker secrets, pas de -e en prodÉvite la fuite dans les logs / inspect
Container hardened — exemple production
docker run \
  --name monapp \
  --user 1001:1001 \
  --read-only \
  --tmpfs /tmp:rw,noexec,nosuid,size=64m \
  --cap-drop ALL \
  --cap-add NET_BIND_SERVICE \
  --security-opt no-new-privileges \
  --security-opt seccomp=/etc/docker/seccomp.json \
  --pids-limit 200 \
  --memory 512m \
  --memory-swap 512m \
  --cpus 1.0 \
  --network mon-reseau \
  --restart unless-stopped \
  -p 3000:3000 \
  monapp:1.0
Scanner une image avec Trivy
# Installer Trivy (scanner de vulnérabilités open-source)
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
  aquasec/trivy:latest image monapp:1.0

# Rapport en JSON
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
  aquasec/trivy:latest image --format json monapp:1.0 > rapport.json

# Docker Scout (intégré dans Docker Desktop)
docker scout cves monapp:1.0
docker scout recommendations monapp:1.0
14

Projet complet — Node.js + PostgreSQL + Nginx

Stack production complète : API Node.js avec Express, base de données PostgreSQL, cache Redis, reverse proxy Nginx avec SSL termination, et un dashboard Adminer accessible en dev.

🌐 Nginx :443 🟢 API :3000 🐘 PostgreSQL :5432 + ⚡ Redis :6379
Structure du projet
mon-projet/
├── Dockerfile                    # multi-stage
├── docker-compose.yml            # base
├── docker-compose.override.yml   # dev
├── docker-compose.prod.yml       # prod
├── .dockerignore
├── .env.example                  # template (committé)
├── .env                          # valeurs réelles (ignoré par git)
├── nginx/
│   ├── nginx.conf
│   └── ssl/
├── src/
│   └── server.js
└── package.json
Dockerfile — multi-stage complet
# ── Stage deps ────────────────────────────────────────
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force

# ── Stage dev (hot reload, debug) ─────────────────────
FROM node:20-alpine AS development
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
ENV NODE_ENV=development
EXPOSE 3000 9229
CMD ["npm", "run", "dev"]

# ── Stage production ───────────────────────────────────
FROM node:20-alpine AS production
WORKDIR /app

RUN addgroup -S appgroup && adduser -S appuser -G appgroup

COPY --from=deps --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --chown=appuser:appgroup . .

ENV NODE_ENV=production \
    PORT=3000

USER appuser
EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
  CMD wget -qO- http://localhost:3000/health || exit 1

CMD ["node", "src/server.js"]
docker-compose.yml — base
version: '3.9'

services:
  nginx:
    image: nginx:1.25-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
      - static_files:/app/public:ro
    depends_on:
      app:
        condition: service_healthy
    networks:
      - frontend
    restart: unless-stopped

  app:
    build:
      context: .
      target: production
    env_file: .env
    environment:
      DATABASE_URL: postgres://${DB_USER}:${DB_PASS}@db:5432/${DB_NAME}
      REDIS_URL: redis://cache:6379
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started
    networks:
      - frontend
      - backend
    restart: unless-stopped
    volumes:
      - static_files:/app/public

  db:
    image: postgres:16-alpine
    env_file: .env
    environment:
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASS}
      POSTGRES_DB: ${DB_NAME}
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./db/init.sql:/docker-entrypoint-initdb.d/01-init.sql:ro
    networks:
      - backend
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
      interval: 10s
      timeout: 5s
      retries: 5

  cache:
    image: redis:7-alpine
    command: redis-server --appendonly yes --maxmemory 128mb --maxmemory-policy allkeys-lru
    volumes:
      - redisdata:/data
    networks:
      - backend
    restart: unless-stopped

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true    # réseau interne, pas d'accès internet direct

volumes:
  pgdata:
  redisdata:
  static_files:
nginx/nginx.conf
events { worker_connections 1024; }

http {
  upstream api {
    server app:3000;          # résolution DNS Compose
    keepalive 32;
  }

  # Redirect HTTP → HTTPS
  server {
    listen 80;
    return 301 https://$host$request_uri;
  }

  server {
    listen 443 ssl http2;
    ssl_certificate     /etc/nginx/ssl/cert.pem;
    ssl_certificate_key /etc/nginx/ssl/key.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;

    # API proxy
    location /api/ {
      proxy_pass         http://api;
      proxy_set_header   Host $host;
      proxy_set_header   X-Real-IP $remote_addr;
      proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header   X-Forwarded-Proto $scheme;
    }

    # Fichiers statiques
    location / {
      root  /app/public;
      try_files $uri $uri/ /index.html;
      gzip on;
      expires 1d;
    }
  }
}
.env.example
# Base de données
DB_USER=monapp
DB_PASS=changeme
DB_NAME=monapp_db

# Application
NODE_ENV=production
JWT_SECRET=changeme_secret_32chars_minimum
SESSION_SECRET=changeme_session_secret

# Redis
REDIS_TTL=3600
Commandes de déploiement
# Premier déploiement
cp .env.example .env   # adapter les valeurs !
docker compose up -d --build

# Mise à jour sans downtime
docker compose pull app
docker compose up -d --no-deps --build app

# Voir l'état de la stack
docker compose ps
docker compose logs -f --tail 50

# Backup base de données
docker compose exec db pg_dump -U $DB_USER $DB_NAME > backup-$(date +%Y%m%d).sql

# Restaurer
docker compose exec -T db psql -U $DB_USER $DB_NAME < backup.sql
15

Troubleshooting

🔴 Container s'arrête immédiatement (exit code 0 ou 1)
CauseProcessus principal se termine. Avec CMD en shell form (CMD node server.js), le shell wrapper capture mal les signaux. Le container pense que tout est terminé.
SolutionUtiliser la forme exec JSON : CMD ["node", "server.js"]. Vérifier les logs : docker logs monapp. Si exit 0, le processus se termine normalement (ajouter une boucle infinie ou laisser tourner le serveur).
🔴 Port déjà utilisé — "bind: address already in use"
CauseUn autre processus ou container utilise déjà le port 8080 (ou autre) sur l'hôte. Ou un container précédent n'a pas été supprimé.
Solutiondocker ps -a pour voir les containers arrêtés. docker rm ancien-container. Ou utiliser un port différent : -p 8081:80. Sur Linux : lsof -i :8080 pour trouver le processus.
🔴 "Cannot connect to the Docker daemon"
CauseLe daemon Docker n'est pas démarré, ou l'utilisateur n'a pas les permissions pour y accéder (groupe docker manquant sur Linux).
SolutionMac/Windows : démarrer Docker Desktop. Linux : sudo systemctl start docker. Ajouter l'utilisateur au groupe : sudo usermod -aG docker $USER puis se reconnecter.
🔴 Container démarre mais l'app ne répond pas
CauseL'app écoute sur 127.0.0.1 (localhost) au lieu de 0.0.0.0. Docker expose bien le port, mais l'app n'accepte pas les connexions venant de l'extérieur du container.
SolutionConfigurer l'app pour écouter sur 0.0.0.0. Ex Node.js : app.listen(3000, '0.0.0.0'). Vérifier avec docker exec monapp netstat -tlnp.
🔴 Build lent — pas de cache utilisé
CauseLe COPY du code source est fait AVANT npm install. Toute modification de code invalide le layer npm install → tout est reinstallé à chaque build.
SolutionOrdonner les instructions du moins changeant au plus changeant : COPY package*.json ./ puis RUN npm ci puis COPY . .. Les dépendances sont cachées tant que package.json ne change pas.
🔴 "OCI runtime exec failed: exec: bash not found"
CauseImages Alpine n'incluent pas bash, uniquement sh. docker exec -it monapp bash échoue.
SolutionUtiliser docker exec -it monapp sh ou docker exec -it monapp /bin/sh. Ou installer bash dans le Dockerfile : RUN apk add --no-cache bash (déconseillé en prod).
🔴 Compose — "service depends_on but won't wait for readiness"
Causedepends_on par défaut attend juste que le container démarre, pas qu'il soit prêt (ex: PostgreSQL peut prendre 3-5s pour accepter des connexions).
SolutionUtiliser depends_on: db: condition: service_healthy avec un healthcheck sur le service db. Ou implémenter un script d'attente wait-for-it.sh dans l'entrypoint.
🔴 Espace disque saturé par les images / layers
CauseAccumulation d'images dangling (non taguées), de containers arrêtés, de volumes orphelins et de build cache.
Solutiondocker system df pour voir la consommation. docker system prune -a --volumes pour tout nettoyer (⚠ irréversible !). En routine : docker image prune -a + docker container prune.
🔴 "permission denied" dans le container
CauseLe container tourne avec un utilisateur non-root (UID 1001) mais le volume monté appartient à root sur l'hôte. Ou les fichiers copiés n'ont pas les bonnes permissions.
SolutionUtiliser COPY --chown=user:group dans le Dockerfile. Pour les volumes : docker run --user $(id -u):$(id -g). Ou dans Compose : user: "1001:1001".
16

Glossaire

Alpine
Distribution Linux ultra-légère (~5 MB) basée sur musl libc. Base idéale pour les images Docker légères (node:20-alpine).
ARG
Variable de build Dockerfile, accessible uniquement pendant docker build, non présente dans l'image finale. Utiliser pour la version, la date de build.
Bind Mount
Montage d'un répertoire de l'hôte dans un container (-v /chemin/hote:/container). Idéal pour le développement avec hot-reload.
BuildKit
Backend de build Docker modernisé (activé par défaut depuis Docker 23). Plus rapide, meilleur cache, support multi-platform, cache SSH.
cgroups
Mécanisme Linux (Control Groups) permettant de limiter les ressources (CPU, RAM, I/O) d'un groupe de processus. Base des limites Docker.
CMD
Instruction Dockerfile définissant la commande par défaut au démarrage du container. Surchargeable à l'exécution avec docker run image commande.
Container
Instance en cours d'exécution d'une image Docker. Isolé via namespaces Linux, éphémère par défaut. Partage le kernel de l'hôte.
Dangling image
Image sans tag (affiché <none>). Résulte d'un rebuild : l'ancien tag est réattribué à la nouvelle image, laissant l'ancienne orpheline.
Distroless
Images Google sans shell ni package manager, contenant uniquement les librairies runtime. Surface d'attaque minimale pour la production.
Docker Compose
Outil de définition et gestion de stacks multi-containers via un fichier YAML. docker compose up lance toute la stack.
Docker Desktop
Application GUI pour Mac/Windows incluant Docker Engine, Compose, Docker Scout, Kubernetes local et un dashboard visuel.
Dockerfile
Fichier texte contenant les instructions pour construire une image Docker, de la base jusqu'à la commande de démarrage.
ENTRYPOINT
Instruction Dockerfile définissant la commande fixe du container. CMD lui sert d'arguments. Moins facilement surchargeable que CMD seul.
ENV
Instruction Dockerfile définissant une variable d'environnement persistée dans l'image et accessible au runtime dans le container.
Healthcheck
Vérification périodique de l'état d'un container. Statuts : starting, healthy, unhealthy. Utilisé par Compose pour les depends_on.
Image
Template immuable en lecture seule composé de layers superposés. Construite depuis un Dockerfile ou tirée d'un registry.
Layer
Couche du système de fichiers produite par chaque instruction Dockerfile (RUN, COPY, ADD). Mise en cache pour accélérer les rebuilds.
Multi-stage build
Technique Dockerfile utilisant plusieurs FROM pour produire une image finale légère ne contenant que les artefacts de build.
Namespace
Mécanisme Linux d'isolation (PID, réseau, montages, utilisateurs). Chaque container a ses propres namespaces = isolation sans VM.
Network
Réseau virtuel Docker permettant la communication entre containers. Bridge personnalisé = DNS automatique par nom de service.
Registry
Serveur de stockage et distribution d'images Docker. Docker Hub est le registry public officiel. ECR, GCR, Harbor sont des alternatives.
Secret
Mécanisme sécurisé pour passer des données sensibles aux containers. Monté en tmpfs dans /run/secrets/, non visible dans docker inspect.
Tag
Étiquette d'une image (ex: nginx:1.25-alpine). latest par défaut mais non recommandé en production (non déterministe).
tmpfs
Montage en mémoire RAM, non persisté sur disque. Utilisé pour les fichiers temporaires sensibles ou les caches haute performance.
Union FS
Système de fichiers par union permettant de superposer les layers d'une image. Implementations : overlay2 (défaut), devicemapper, btrfs.
Volume
Stockage persistant géré par Docker, indépendant du cycle de vie du container. Survit à docker rm, partageable entre containers.
WORKDIR
Instruction Dockerfile définissant le répertoire courant pour les instructions suivantes (COPY, RUN, CMD). Crée le dossier si inexistant.