Découverte de Docker
Comprendre les conteneurs, écrire un Dockerfile, orchestrer plusieurs services avec Compose, et déployer dans un environnement reproductible.
À la fin du cours, tu sais
- Expliquer ce que résout Docker et ce qui le différencie des VMs
- Écrire un Dockerfile multi-stage propre
- Orchestrer plusieurs services avec docker compose
- Persister des données avec des volumes
- Appliquer les bonnes pratiques de sécurité et de taille d'image
Prérequis
- Être à l'aise avec un terminal et la ligne de commande
- Avoir déjà lancé une application en local (Node, Python, Java, peu importe)
- Pas besoin de connaître les VMs ni Linux en profondeur
Chapitre 1
Pourquoi Docker ?
Avant le comment, le pourquoi. Docker n'est pas un outil magique, c'est une réponse précise à un problème concret que tu as déjà rencontré.
Le problème : « ça marche sur ma machine »
Le pire ennemi du "ça marche chez moi", c'est la divergence d'environnement. Ton code passe en local, casse en CI, repasse en staging, recasse en prod. Tu perds des heures à diagnostiquer une version de Node qui change, une dépendance système absente, une variable d'environnement oubliée.
Cette divergence vient toujours des mêmes coupables : version d'un runtime, librairies installées au niveau de l'OS, configuration laissée à l'implicite, base de données absente ou en mauvaise version.
La solution : encapsuler l'environnement
L'idée est simple : empaqueter le code et son environnement (runtime, librairies, configuration) dans un artefact reproductible. Cet artefact se lance n'importe où à l'identique, sur ta machine, sur ton CI, en production.
C'est exactement ce que fait Docker. Tu décris l'environnement une fois dans un fichier texte, tu construis une image, et cette image tourne partout pareil.
VM vs conteneur
Les VMs et les conteneurs résolvent le même problème (isoler une application), mais à des coûts très différents.
- Une VM virtualise tout le matériel et embarque un OS complet (kernel + userspace). Démarrage en minutes, empreinte en Go.
- Un conteneur partage le kernel de l'OS hôte et n'embarque que l'application avec ses dépendances. Démarrage en secondes, empreinte en Mo.
Conséquence pratique
Cas d'usage typiques
- Environnement de dev cohérent entre tous les membres d'une équipe
- Pipeline CI/CD reproductible : les tests tournent dans le même environnement que la prod
- Déploiement en production (microservices, edge, fonctions serverless)
- Isolation de processus : sandboxer un script non sûr, faire tourner une vieille version d'une lib
Chapitre 2
Les concepts fondamentaux
Trois mots à bien distinguer : image, conteneur, registry. Le reste de Docker tient autour.
L'image
Une image est un template immuable. Elle contient l'OS minimal, le runtime (Node, Python, JVM...), ton code, les dépendances. Tu construis une image une fois, puis tu peux la lancer autant de fois que tu veux.
Métaphore utile : l'image est le moule, le conteneur est la pâtisserie. Le moule (image) ne change pas, tu peux en sortir plusieurs gâteaux (conteneurs) identiques.
Le conteneur
Un conteneur est une instance d'image en cours d'exécution. Il a son propre filesystem (en couches superposées), son réseau, ses processus. Il vit, fait son travail, meurt. Sans affecter l'image source.
Implication importante : les conteneurs sont éphémères. Si tu écris des données dans le filesystem du conteneur et que tu le supprimes, les données disparaissent. Pour persister, il faut des volumes (on verra ça au chapitre 7).
Le registry
Un registry est un dépôt centralisé d'images. Le plus connu est Docker Hub (docker.io). GitHub Container Registry (ghcr.io) est aussi très utilisé. Tu peux héberger ton propre registry privé (Harbor, Gitea, AWS ECR...).
Format complet d'un nom d'image : registry/namespace/repo:tag. Par exemple docker.io/library/postgres:16-alpine. Quand tu écris juste postgres:16-alpine, Docker complète avec Docker Hub par défaut.
Layers et cache de build
Docker construit les images en couches : chaque instruction du Dockerfile crée une layer. Ces layers sont mises en cache. Si tu re-builds sans rien changer, le build est instantané.
À garder en tête
Chapitre 3
Installer Docker
Selon ton OS, l'installation diffère. Voici les chemins recommandés en 2026.
Docker Desktop (Mac & Windows)
Docker Desktop est l'interface tout-en-un : GUI + Docker Engine + Compose + Kubernetes optionnel. Téléchargeable sur docker.com/products/docker-desktop.
Licence Docker Desktop
Docker Engine (Linux)
Sur Linux, on installe directement le moteur, sans GUI. Le mieux est de suivre le repo officiel pour avoir la dernière version stable, pas celle (souvent vieille) des dépôts de la distrib.
Sur Ubuntu, en une seule ligne via le script d'install officiel :
curl -fsSL https://get.docker.com | shAjoute ensuite ton utilisateur·ice au groupe docker pour ne plus avoir à taper sudo à chaque commande :
sudo usermod -aG docker $USER
newgrp dockerVérifier l'installation
docker version
docker run hello-worldSi hello-world affiche son message, tout est bon : Docker a téléchargé l'image, créé un conteneur, exécuté le programme, affiché le message, puis arrêté le conteneur.
Chapitre 4
Tes premières commandes
La CLI Docker a plus de 50 commandes. 90 % de ton usage quotidien tient en une dizaine. Voilà l'essentiel.
docker run, la commande à tout faire
docker run [options] image [commande]Les options que tu vas utiliser tous les jours :
-d: détaché, lance en arrière-plan-p HÔTE:CONTENEUR: map un port (ex:-p 8080:80)--name NOM: nomme le conteneur (sinon Docker invente)-v VOLUME: monte un volume (on en parle au chapitre 7)-e KEY=value: passe une variable d'environnement--rm: supprime le conteneur quand il s'arrête-it: interactif + terminal (utile pour lancer un shell)
Exemple concret : lance un PostgreSQL prêt à l'emploi en une commande.
docker run -d \
--name pg \
-p 5432:5432 \
-e POSTGRES_PASSWORD=secret \
postgres:16-alpineTu as maintenant un PostgreSQL accessible sur localhost:5432. Pas d'install, pas de configuration système.
Lister, arrêter, supprimer
docker ps # conteneurs en cours
docker ps -a # tous les conteneurs, même arrêtés
docker stop pg # arrête proprement
docker start pg # redémarre
docker restart pg # stop + start
docker rm pg # supprime (le conteneur doit être arrêté)
docker rm -f pg # force la suppression d'un conteneur qui tourneManipuler les images
docker images # liste les images locales
docker pull node:22 # télécharge une image sans la lancer
docker rmi node:22 # supprime une image
docker image prune # supprime toutes les images non utiliséesInspecter et déboguer
docker logs pg # affiche les logs
docker logs -f pg # suit les logs en temps réel (Ctrl+C pour sortir)
docker exec -it pg bash # ouvre un shell dans le conteneur
docker inspect pg # détails complets en JSON
docker stats # CPU/RAM en temps réelRéflexe de debug numéro 1
docker run -d, relance-le sans -d pour voir l'erreur directement dans ton terminal. Tu gagnes 10 minutes de tâtonnement.Chapitre 5
Le Dockerfile
Le Dockerfile est le fichier texte qui décrit comment construire ton image. C'est la recette de ton environnement, versionnée avec ton code.
Les instructions principales
FROM: image de base (souvent une distribution Linux minimale avec un runtime)WORKDIR: répertoire de travail dans le conteneurCOPY src dst: copie depuis l'hôte vers l'imageRUN: exécute une commande au moment du build (install, compilation)ENV: variable d'environnement disponible au build et au runtimeEXPOSE: documente un port (purement informatif, ne le publie pas)CMD: commande par défaut au lancement (peut être surchargée)ENTRYPOINT: commande qui s'exécute toujours,CMDdevient ses argumentsUSER: change l'utilisateur d'exécution (par défaut root, à éviter en prod)HEALTHCHECK: commande de vérification de santé du conteneur
Exemple complet : application Node.js
FROM node:22-alpine
WORKDIR /app
# Layer cachée tant que package.json ne change pas
COPY package*.json ./
RUN npm ci --omit=dev
# Le code applicatif change souvent, on le copie après
COPY . .
EXPOSE 3000
USER node
CMD ["node", "server.js"]Ordre des instructions = cache
COPY package*.json ./ avant COPY . . permet de cacher l'étape npm install tant que les dépendances ne changent pas. Sans ça, tu rebuilds tout à chaque modif de code. C'est l'optimisation Docker qui a le plus d'impact..dockerignore : ne pas envoyer tout le contexte
Comme .gitignore, le .dockerignore exclut des fichiers du contexte de build envoyé au daemon Docker. Crucial pour la performance et la taille des images.
node_modules
.git
.env
*.log
.DS_Store
dist
coverage
Dockerfile
.dockerignoreMulti-stage builds : images de prod légères
Le multi-stage permet de build dans une image lourde (avec tous les outils) puis de ne copier que le résultat dans une image finale minimale. Tu passes facilement de 1,2 Go à 200 Mo.
# Stage 1 : build
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2 : runtime minimal
FROM node:22-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./
RUN npm ci --omit=dev
USER node
CMD ["node", "dist/server.js"]Résultat : l'image finale ne contient que le code compilé et les dépendances de prod. Pas de devDependencies, pas de code source, pas de toolchain.
Chapitre 6
Docker Compose pour orchestrer
Quand ton app a plusieurs services (API + DB + cache + reverse proxy), lancer chaque conteneur à la main devient pénible. Compose orchestre tout ça en un seul fichier YAML.
docker-compose.yml
Le fichier décrit tes services, leurs images, leurs ports, leurs volumes, leurs interconnexions. Tu lances tout d'un seul docker compose up.
services:
web:
build: .
ports:
- "3000:3000"
environment:
DATABASE_URL: postgres://user:pass@db:5432/app
depends_on:
db:
condition: service_healthy
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: app
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d app"]
interval: 5s
timeout: 3s
retries: 5
volumes:
db-data:Plus de version: en haut
version: est obsolète. Si tu vois ça dans un vieux tuto, supprime-le, ça fait juste un warning maintenant.Les commandes essentielles
docker compose up -d # lance tout en arrière-plan
docker compose up --build # rebuild les images avant de lancer
docker compose down # arrête et supprime les conteneurs
docker compose down -v # supprime aussi les volumes (attention !)
docker compose logs -f web # suit les logs d'un service
docker compose exec web sh # shell dans un service en cours
docker compose ps # liste les services
docker compose restart web # redémarre un servicedocker compose vs docker-compose
docker compose (sans tiret, c'est un sous-commande du CLI). Le vieux docker-compose (avec tiret) est déprécié mais fonctionne encore via un plugin. Préfère la nouvelle syntaxe dans tes scripts.Le réseau automatique
Compose crée automatiquement un réseau privé entre tes services. Le nom du service devient le hostname : depuis web, tu peux joindre db avec postgres://user:pass@db:5432. Pas besoin d'IP, pas besoin de localhost.
Chapitre 7
Volumes et données persistantes
Un conteneur est éphémère : supprime-le, son filesystem disparaît. Pour les données qui doivent survivre, il faut un volume.
Deux types de stockage
- Bind mount : tu montes un dossier de ton hôte directement dans le conteneur. Utile en dev pour voir les modifications de code en temps réel.
- Named volume : Docker gère le stockage dans sa propre zone (
/var/lib/docker/volumes). Plus propre pour la production.
docker run -v $(pwd):/app node:22 npm testdocker volume create app-data
docker run -v app-data:/data my-appQuand utiliser quoi
- Bind mount : développement (hot reload), scripts ponctuels, montage de fichiers de config depuis l'hôte
- Named volume : bases de données, uploads d'utilisateur·ices, cache persistant, anything qui doit survivre au cycle de vie d'un conteneur
Performance sur Mac/Windows
node_modules), privilégie un named volume ou utilise OrbStack qui optimise ça.Sauvegarder un volume
docker run --rm \
-v app-data:/source:ro \
-v $(pwd):/backup \
busybox tar czf /backup/data.tar.gz -C /source .On lance un conteneur jetable (--rm) avec le volume monté en lecture seule et le dossier courant en backup, puis on tar.gz le contenu. Astuce classique pour faire des dumps réguliers.
Chapitre 8
Les bonnes pratiques en production
Faire tourner Docker en dev est facile. Le mettre en prod proprement demande une poignée de réflexes. En voilà l'essentiel.
Images légères
- Utilise une base
alpine(5 Mo) oudistroless(image sans shell, ultra-minimale) plutôt que la base par défaut - Multi-stage build pour ne garder que le strict nécessaire au runtime
- Combine les
RUNavec&&pour éviter les layers inutiles - Nettoie les caches dans la même
RUN(apt clean,npm cache clean)
Sécurité
- Ne lance jamais en root en production : ajoute
USER node(ou autre user non-root) dans ton Dockerfile - Ne mets jamais de secret dans le Dockerfile ni dans l'image (utilise les variables d'env au runtime ou un secret manager type Doppler, AWS Secrets Manager)
- Pinne les versions :
node:22.5.1-alpine, pasnode:latest(build reproductible, pas de surprise) - Scanne tes images :
docker scout cvesoutrivy image my-app
Healthchecks
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
CMD curl -f http://localhost:3000/health || exit 1Sans healthcheck, ton orchestrateur (Kubernetes, Compose, ECS, Nomad) ne sait pas si ton app est vraiment prête à servir du trafic. Il routera des requêtes vers un conteneur cassé.
Logs : stdout, toujours
N'écris pas les logs dans des fichiers à l'intérieur du conteneur. Ils seront perdus à la mort du conteneur, et invisibles depuis l'extérieur. Écris sur stdout et stderr. Docker (et tout orchestrateur) collecte ça automatiquement et le route vers ton système de logs centralisé.
Versionne tes images
- En CI, tagge avec le SHA du commit et un tag sémantique (ex:
1.4.2) - Garde
:latestpour les builds dev uniquement, jamais en prod - Pousse sur ton registry et garde un historique versionné pour pouvoir rollback rapidement
À retenir pour la prod
🛠️ Exercice optionnel
Dockeriser une API Express avec PostgreSQL
Tu as une petite application Node.js Express qui se connecte à PostgreSQL pour stocker une liste de tâches. Le but : tout faire tourner dans Docker, sans installer Node ni Postgres sur ta machine, avec une commande unique pour tout lancer.
Ta mission
- Écris un
Dockerfilemulti-stage pour l'API Express (image finale légère, user non-root). - Écris un
docker-compose.ymlqui lance l'API + Postgres avec :- persistance des données Postgres dans un volume nommé
- healthcheck sur Postgres
- l'API qui attend que Postgres soit prêt avant de démarrer
- Vérifie que
docker compose up --builddémarre tout et que l'API peut requêter la DB.
Tu bloques ? Des indices, à dévoiler quand tu en as besoin.
Indice 1
Indice masqué.
Indice 2
Indice masqué.
Indice 3
Indice masqué.
✅ QCM de fin de cours
Teste tes acquis
10 questions, plusieurs réponses parfois possibles. Coche tout ce qui te semble juste, puis valide pour voir ton score et les explications.
- 1
Quelle est la différence principale entre une image et un conteneur Docker ?
- 2
Laquelle de ces commandes lance un conteneur en arrière-plan ?
- 3
Pourquoi mettre
COPY package*.jsonavantCOPY . .dans un Dockerfile Node ? - 4
Quelle directive Dockerfile permet d'exécuter le conteneur avec un utilisateur·ice non-root ?
- 5
À quoi sert un multi-stage build ?
- 6
Quelle option de
docker runsupprime le conteneur à la fin de son exécution ? - 7
Dans un
docker-compose.yml, comment ton serviceapipeut-il joindre le servicedb? - 8
Tu trouves
version: "3.8"en haut d'undocker-compose.yml. Que faire ? - 9
Pourquoi privilégier des images alpine ou distroless en production ?
- 10
Bonne pratique : où écrire les logs de ton application dans un conteneur ?
Tu peux laisser des questions sans réponse, elles compteront comme fausses.
Tu veux ce cours pour ton équipe ?
Je peux adapter et animer ce cours pour tes formateur·ices ou tes apprenant·es, en présentiel ou en distanciel. Parlons-en pendant l'audit gratuit.
Réserver un audit gratuit →