Cours Docker Complet 🐳

Les 12 chapitres essentiels — conteneurs, images, Compose, volumes, réseau et déploiement

12
Chapitres
120+
Commandes
Docker 27+
Version
A1→C2
Niveaux

CHAPITRE 01

Introduction et installation

🐳 Qu'est-ce que Docker ?
# Installer Docker Desktop
# → https://www.docker.com/products/docker-desktop/
# macOS, Windows, Linux — interface graphique + CLI

# Vérifier l'installation
docker –version # Docker version 27.x
docker compose version # Docker Compose v2.x

# Tester avec le conteneur hello-world
docker run hello-world
# → Télécharge l'image, crée un conteneur, exécute, affiche un message

Docker empaquette une application avec toutes ses dépendances dans un conteneur — un environnement isolé, léger et portable. « Ça marche sur ma machine » → « Ça marche partout ». Créé par Solomon Hykes en 2013, Docker est devenu le standard de déploiement. Utilisé par Google, Netflix, Spotify, Uber, PayPal et 90% des entreprises tech.

⚖️ Conteneur vs Machine Virtuelle
Aspect Conteneur Docker Machine Virtuelle
Démarrage Secondes Minutes
Taille Mo (image Alpine = 5 Mo) Go (OS complet)
Isolation Partage le kernel de l'hôte OS complet séparé
Performance Quasi-native (pas d'hyperviseur) Overhead de virtualisation
Densité Dizaines de conteneurs par serveur Quelques VMs
Portabilité Image identique partout Format VM variable

Un conteneur n'est PAS une mini VM. C'est un processus Linux isolé grâce aux namespaces (isolation) et cgroups (limites ressources). Il partage le kernel de l'hôte — c'est pour ça qu'il est si léger et rapide.

🧠 Quiz
Quelle est la différence entre une image et un conteneur ?
Une image = un template en lecture seule (comme une classe en POO). Un conteneur = une instance en cours d'exécution de cette image (comme un objet). Vous pouvez créer plusieurs conteneurs à partir d'une même image. L'image est immuable, le conteneur a un état (fichiers écrits, mémoire, processus).

CHAPITRE 02

Premiers conteneurs

▶️ Lancer et gérer des conteneurs
# Lancer un conteneur
docker run nginx # En premier plan (bloquant)
docker run -d nginx # Détaché (arrière-plan)
docker run -d –name mon-nginx nginx # Avec un nom

# Mapper un port → accès depuis le navigateur
docker run -d -p 8080:80 nginx
# → http://localhost:8080 affiche la page Nginx
# Format : -p PORT_HOTE:PORT_CONTENEUR

# Lancer un shell interactif dans un conteneur
docker run -it ubuntu bash
# -i = interactif (garde stdin ouvert)
# -t = pseudo-terminal (TTY)

# Exécuter dans un conteneur en cours
docker exec -it mon-nginx bash
docker exec mon-nginx cat /etc/nginx/nginx.conf

🔧 Gérer les conteneurs
# Lister
docker ps # Conteneurs en cours
docker ps -a # Tous (y compris arrêtés)

# Arrêter / Démarrer / Redémarrer
docker stop mon-nginx # Arrêt gracieux (SIGTERM)
docker kill mon-nginx # Arrêt immédiat (SIGKILL)
docker start mon-nginx # Redémarrer un conteneur arrêté
docker restart mon-nginx

# Logs
docker logs mon-nginx # Tous les logs
docker logs -f mon-nginx # Suivre en temps réel (tail -f)
docker logs –tail 50 mon-nginx # 50 dernières lignes

# Inspecter
docker inspect mon-nginx # JSON complet (IP, volumes, config)
docker stats # CPU, RAM, réseau en temps réel

# Supprimer
docker rm mon-nginx # Supprimer un conteneur arrêté
docker rm -f mon-nginx # Forcer (même en cours)

# Nettoyage global
docker system prune -a # Tout supprimer (conteneurs, images, cache)

Les conteneurs sont éphémères par défaut. Quand vous supprimez un conteneur, toutes les données écrites dedans sont perdues. Pour persister des données, utilisez des volumes (chapitre 5).

CHAPITRE 03

Images

📦 Gérer les images
# Télécharger une image depuis Docker Hub
docker pull node:20-alpine # Node.js 20 sur Alpine Linux (léger)
docker pull postgres:16 # PostgreSQL 16
docker pull python:3.12-slim # Python 3.12 (version slim)

# Lister les images locales
docker images
docker image ls

# Supprimer une image
docker rmi node:20-alpine
docker image prune # Supprimer les images inutilisées

🏷️ Tags et variantes
Tag Description Taille
node:20 Image complète (Debian) ~1 Go
node:20-slim Debian allégée ~200 Mo
node:20-alpine Alpine Linux (recommandé) ~50 Mo
python:3.12 Image complète ~1 Go
python:3.12-slim Slim (recommandé Python) ~150 Mo
python:3.12-alpine Alpine (⚠️ problèmes avec certains packages) ~50 Mo

Utilisez toujours un tag précis (node:20-alpine) au lieu de node:latest. Le tag latest change à chaque mise à jour — votre build pourrait casser un jour sans raison. Alpine est recommandé pour sa légèreté sauf pour Python (préférer slim à cause de la compilation de packages C).

CHAPITRE 04

Dockerfile

🏗️ Écrire un Dockerfile — Node.js
# Dockerfile

# 1. Image de base
FROM node:20-alpine

# 2. Dossier de travail dans le conteneur
WORKDIR /app

# 3. Copier les fichiers de dépendances EN PREMIER (cache Docker)
COPY package*.json ./

# 4. Installer les dépendances
RUN npm ci –only=production

# 5. Copier le reste du code
COPY . .

# 6. Port exposé (documentation)
EXPOSE 3000

# 7. Utilisateur non-root (sécurité)
USER node

# 8. Commande de démarrage
CMD [« node », « server.js »]

# Construire l'image
docker build -t mon-app:1.0 .

# Lancer
docker run -d -p 3000:3000 –name api mon-app:1.0

# .dockerignore — ne pas copier dans l'image
node_modules
.git
.env
npm-debug.log
Dockerfile
docker-compose.yml

L'ordre des instructions est crucial pour le cache. Docker met en cache chaque couche (layer). Si un fichier change, Docker reconstruit à partir de cette couche. Copiez package.json AVANT le code source : les dépendances ne sont réinstallées que si package.json change. Le code source change souvent → dernière couche.

🐍 Dockerfile — Python
FROM python:3.12-slim

WORKDIR /app

# Empêcher Python de bufferiser stdout (logs en temps réel)
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1

# Dépendances système si nécessaires
RUN apt-get update && apt-get install -y –no-install-recommends \
gcc libpq-dev \
&& rm -rf /var/lib/apt/lists/*

# Dépendances Python (cache)
COPY requirements.txt .
RUN pip install –no-cache-dir -r requirements.txt

# Code
COPY . .

EXPOSE 8000

CMD [« gunicorn », « app:create_app() », « –bind », « 0.0.0.0:8000 »]

📋 Instructions Dockerfile
Instruction Rôle Exemple
FROM Image de base (obligatoire, 1ère ligne) FROM node:20-alpine
WORKDIR Dossier de travail WORKDIR /app
COPY Copier des fichiers hôte → image COPY . .
RUN Exécuter pendant le build RUN npm ci
CMD Commande par défaut au démarrage CMD [« node », « app.js »]
ENTRYPOINT Commande fixe (non remplaçable) ENTRYPOINT [« python »]
ENV Variable d'environnement ENV NODE_ENV=production
EXPOSE Port documenté (informatif) EXPOSE 3000
USER Utilisateur non-root (sécurité) USER node
ARG Variable de build (pas disponible au runtime) ARG NODE_VERSION=20

CHAPITRE 05

Volumes et persistance

💾 3 types de montage
# 1. Named Volume — géré par Docker (production)
docker run -d \
-v pgdata:/var/lib/postgresql/data \
postgres:16
# Docker stocke les données dans /var/lib/docker/volumes/pgdata/

# 2. Bind Mount — dossier hôte → conteneur (développement)
docker run -d \
-v $(pwd)/src:/app/src \
-p 3000:3000 \
mon-app
# Modifications locales → visibles dans le conteneur (hot reload)

# 3. tmpfs — en mémoire (données temporaires)
docker run -d \
–tmpfs /tmp \
mon-app

Type Syntaxe Usage
Named Volume -v pgdata:/data BDD production, données persistantes
Bind Mount -v ./src:/app/src Développement, hot reload
tmpfs –tmpfs /tmp Données temporaires, secrets
# Gérer les volumes
docker volume ls # Lister
docker volume inspect pgdata # Détails
docker volume rm pgdata # Supprimer
docker volume prune # Supprimer les orphelins

CHAPITRE 06

Réseau

🌐 Communication entre conteneurs
# Créer un réseau custom
docker network create mon-reseau

# Lancer des conteneurs sur le même réseau
docker run -d –name db \
–network mon-reseau \
-e POSTGRES_PASSWORD=secret \
postgres:16

docker run -d –name api \
–network mon-reseau \
-p 3000:3000 \
-e DATABASE_URL=postgresql://postgres:secret@db:5432/postgres \
mon-app

# « db » est résolu automatiquement en IP par Docker DNS
# → Les conteneurs se parlent par leur NOM

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

Sur un réseau Docker custom, les conteneurs se découvrent par leur nom. C'est la résolution DNS intégrée de Docker. Le réseau par défaut (bridge) ne supporte PAS la résolution DNS — utilisez toujours un réseau custom. Docker Compose crée automatiquement un réseau pour vos services.

CHAPITRE 07

Dockerfile avancé

🏗️ Multi-stage builds
# Multi-stage = image finale ultra-légère
# Étape 1 : Build (grosse image avec outils de compilation)
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Étape 2 : Production (image légère, seulement le résultat)
FROM node:20-alpine
WORKDIR /app
COPY –from=builder /app/dist ./dist
COPY –from=builder /app/node_modules ./node_modules
COPY package*.json ./
USER node
EXPOSE 3000
CMD [« node », « dist/server.js »]

# Résultat : image finale ne contient PAS le code source,
# les devDependencies, ni les outils de build

Le multi-stage divise le build en étapes. L'image finale ne contient que le strict nécessaire — pas de compilateurs, pas de devDependencies, pas de code source. Résultat : images 2-10x plus petites, surface d'attaque réduite, déploiement plus rapide.

🏥 Healthcheck
# Vérifier que l'app est healthy
HEALTHCHECK –interval=30s –timeout=5s –retries=3 \
CMD wget –quiet –tries=1 –spider http://localhost:3000/health || exit 1

# Ou avec curl
HEALTHCHECK –interval=30s –timeout=5s –retries=3 \
CMD curl -f http://localhost:3000/health || exit 1

# Vérifier le statut
docker ps # → affiche « healthy » ou « unhealthy »

CHAPITRE 08

Docker Compose

🎼 Orchestrer plusieurs services
# docker-compose.yml (ou compose.yml)

services:
# Application Node.js
api:
build: .
ports:
« 3000:3000 »
environment:
– DATABASE_URL=postgresql://postgres:secret@db:5432/mydb
– REDIS_URL=redis://cache:6379
– NODE_ENV=production
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
restart: unless-stopped

# Base de données PostgreSQL
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: mydb
POSTGRES_USER: postgres
POSTGRES_PASSWORD: secret
volumes:
– pgdata:/var/lib/postgresql/data
ports:
« 5432:5432 »
healthcheck:
test: [« CMD-SHELL », « pg_isready -U postgres »] interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped

# Cache Redis
cache:
image: redis:7-alpine
ports:
« 6379:6379 »
restart: unless-stopped

volumes:
pgdata:

# Commandes Compose
docker compose up -d # Démarrer tout en arrière-plan
docker compose up -d –build # Reconstruire les images avant
docker compose down # Arrêter et supprimer les conteneurs
docker compose down -v # + supprimer les volumes
docker compose logs -f api # Logs d'un service
docker compose ps # Statut des services
docker compose exec api sh # Shell dans un service
docker compose restart api # Redémarrer un service

Docker Compose définit toute votre stack dans un seul fichier YAML. Un seul docker compose up lance l'app, la BDD, le cache, le reverse proxy — tout configuré et connecté. Compose crée automatiquement un réseau entre vos services : db est résolu en IP depuis le service api.

CHAPITRE 09

Compose avancé

🔧 Dev vs Production
# compose.yml — base commune
services:
api:
build: .
ports:
« 3000:3000 »
depends_on:
– db

db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: secret
volumes:
– pgdata:/var/lib/postgresql/data

volumes:
pgdata:

# compose.override.yml — surcharges développement (chargé auto)
services:
api:
build:
target: builder # Stage de dev (avec devDeps)
volumes:
– ./src:/app/src # Hot reload
environment:
– NODE_ENV=development
command: npm run dev # Commande de dev (nodemon)

db:
ports:
« 5432:5432 » # Exposer pour les outils locaux

# compose.prod.yml — surcharges production
services:
api:
restart: always
environment:
– NODE_ENV=production
deploy:
resources:
limits:
cpus: « 1.0 »
memory: 512M

# Lancer en prod
docker compose -f compose.yml -f compose.prod.yml up -d

🔒 Variables d'environnement
# .env — variables pour Compose (pas dans l'image !)
POSTGRES_PASSWORD=mon_secret_123
NODE_ENV=production
API_PORT=3000

# compose.yml
services:
api:
ports:
« $:3000 »
env_file:
– .env # Injecter toutes les variables

Ne commitez JAMAIS le fichier .env avec des secrets. Ajoutez .env dans .gitignore. Fournissez un .env.example avec des valeurs factices pour documenter les variables nécessaires. En production, utilisez les secrets de votre plateforme (Docker Secrets, AWS SSM, Vault).

CHAPITRE 10

Registres et CI/CD

☁️ Publier une image
# Docker Hub — registre public par défaut
docker login
docker build -t monuser/mon-app:1.0 .
docker push monuser/mon-app:1.0

# GitHub Container Registry (ghcr.io)
docker login ghcr.io -u USERNAME
docker build -t ghcr.io/monuser/mon-app:1.0 .
docker push ghcr.io/monuser/mon-app:1.0

# Taguer une image
docker tag mon-app:1.0 monuser/mon-app:latest
docker tag mon-app:1.0 monuser/mon-app:1.0

⚙️ GitHub Actions + Docker
# .github/workflows/docker.yml
name: Build & Push Docker

on:
push:
branches: [main]

jobs:
build:
runs-on: ubuntu-latest
steps:
uses: actions/checkout@v4

name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: $
password: $

name: Build and push
uses: docker/build-push-action@v6
with:
push: true
tags: monuser/mon-app:latest,monuser/mon-app:$

CHAPITRE 11

Production et orchestration

🚀 Déployer en production
Plateforme Complexité Idéal pour
Docker Compose + VPS Simple Side projects, petites apps
Railway / Render / Fly.io Simple PaaS avec support Docker natif
AWS ECS Moyen Production AWS, auto-scaling
Google Cloud Run Simple Serverless conteneurisé, pay-per-use
Kubernetes (K8s) Complexe Grande échelle, multi-services, enterprise

Kubernetes n'est nécessaire que pour les grandes équipes avec beaucoup de services. Pour la plupart des projets, Docker Compose sur un VPS (avec un reverse proxy Traefik ou Caddy) suffit. Ne sur-ingéniérez pas. Cloud Run et Fly.io sont d'excellentes alternatives managed.

🔒 Sécurité conteneur
# 1. Utilisateur non-root
USER node # Ne pas exécuter en root

# 2. Image minimale
FROM node:20-alpine # Moins de packages = moins de vulnérabilités

# 3. Scanner les vulnérabilités
docker scout cves mon-app:1.0

# 4. Ne pas stocker de secrets dans l'image
# ❌ ENV API_KEY=secret123
# ✅ Passer au runtime : docker run -e API_KEY=$API_KEY

# 5. Read-only filesystem
docker run –read-only –tmpfs /tmp mon-app

CHAPITRE 12

Bonnes pratiques

📋 Commandes mémo
Action Commande
Lancer un conteneur docker run -d -p 8080:80 –name web nginx
Shell dans un conteneur docker exec -it web sh
Voir les logs docker logs -f web
Construire une image docker build -t app:1.0 .
Lancer la stack Compose docker compose up -d –build
Arrêter la stack docker compose down
Tout nettoyer docker system prune -a
Pousser une image docker push user/app:1.0
✅ Bonnes pratiques

✅ À FAIRE
• Tags précis (node:20-alpine, pas latest)
• Multi-stage builds (images légères)
.dockerignore (exclure node_modules, .git)
USER node (non-root)
• Copier package.json AVANT le code (cache)
docker compose pour le dev local
• Healthchecks dans le Dockerfile
• Named volumes pour les BDD
• Scanner les vulnérabilités (docker scout)
.env.example versionné (pas .env)

❌ À ÉVITER
FROM node:latest (imprévisible)
• Exécuter en root dans le conteneur
• Secrets dans le Dockerfile (ENV API_KEY=…)
• Ignorer le .dockerignore
docker compose down -v en prod (perte de données)
• Images inutilement grosses (utiliser alpine/slim)
• Tout dans un seul conteneur (1 service = 1 conteneur)
npm install au lieu de npm ci (build non reproductible)
• Pas de logs (docker logs doit fonctionner)
• Kubernetes pour un side project

🧠 Quiz
Pourquoi copier package.json AVANT le reste du code ?
Docker met en cache chaque couche (layer) du Dockerfile. Si une couche ne change pas, Docker la réutilise. En copiant package.json d'abord, le RUN npm ci n'est relancé que si les dépendances changent. Si seul le code source change, Docker saute directement à COPY . . — le build passe de minutes à secondes.
Quelle est la différence entre CMD et ENTRYPOINT ?
CMD = commande par défaut, remplaçable au runtime (docker run mon-app bash remplace CMD). ENTRYPOINT = commande fixe, les arguments de docker run sont ajoutés après. Utilisez CMD pour la commande de démarrage habituelle. Utilisez ENTRYPOINT quand votre conteneur est un « exécutable » (ex: ENTRYPOINT [« python »] + CMD [« app.py »]).
Named Volume vs Bind Mount — quand utiliser quoi ?
Named Volume = géré par Docker, performant, portable, idéal pour la production (BDD, fichiers uploadés). Docker gère le cycle de vie. Bind Mount = monte un dossier de votre machine dans le conteneur, idéal pour le développement (hot reload : modifiez le code localement, le conteneur le voit immédiatement). Ne jamais utiliser de bind mount en production.

Cours Docker Complet — Conteneurs, images, Compose, volumes, réseau et déploiement

Référence : docs.docker.com | Docker Hub | Compose