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
Étape 1 sur 11 : Pourquoi Docker ?
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é.
Avant d'apprendre le comment de Docker, il faut comprendre le pourquoi, sinon l'outil reste une suite de commandes magiques qu'on récite sans les saisir. Docker n'est pas une mode : c'est une réponse précise à un problème que tu as forcément déjà rencontré si tu as codé à plusieurs, ou même simplement changé de machine. Ce chapitre pose ce problème et l'idée qui le résout ; tout le reste du cours n'est que la mise en œuvre de cette idée.
Le problème a un nom célèbre : « ça marche sur ma machine ». Ton code fonctionne en local, casse en intégration continue, repasse en préproduction, recasse en production — et tu perds des heures à traquer une version de Node qui diffère, une bibliothèque système absente, une variable d'environnement oubliée. La cause est toujours la même : la divergence d'environnement. La solution de Docker est élégante : plutôt que d'espérer que tous les environnements soient identiques, on empaquette le code avec son environnement (le runtime, les bibliothèques, la configuration) dans un artefact reproductible qui tourne partout à l'identique. Tu décris l'environnement une fois, dans un fichier texte, et cet emballage se lance chez toi, en CI et en prod exactement de la même façon.
Reste à comprendre pourquoi on parle de conteneurs et pas de machines virtuelles, qui isolent aussi les applications. La différence est une question de coût. Une machine virtuelle (VM) virtualise tout le matériel et embarque un système d'exploitation complet : elle démarre en minutes et pèse des gigaoctets. Un conteneur, lui, partage le noyau du système hôte et n'embarque que l'application et ses dépendances : il démarre en secondes et pèse des mégaoctets. La conséquence est spectaculaire — tu peux lancer dix conteneurs sur ton laptop sans le faire ramer, là où dix VM le mettraient à genoux. C'est cette légèreté qui rend la conteneurisation viable aussi bien pour le développement local que pour les architectures en microservices.
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.
Docker s'articule autour de trois concepts qu'il faut absolument distinguer, car les confondre est la source de la plupart des incompréhensions : l'image, le conteneur et le registry. Une fois ce trio clair, tout le reste de Docker s'organise naturellement autour. Prends le temps de bien saisir la différence entre les deux premiers en particulier — c'est la clé de voûte de tout le cours.
Une image est un modèle immuable : elle contient un système minimal, le runtime, ton code et ses dépendances, figés une fois pour toutes. Un conteneur est une instance en cours d'exécution de cette image, avec son propre système de fichiers, son réseau, ses processus. La métaphore la plus parlante est celle du moule et du gâteau : l'image est le moule, qui ne change pas ; les conteneurs sont les gâteaux, que tu peux sortir en autant d'exemplaires identiques que tu veux. Conséquence essentielle et souvent découverte à la dure : les conteneurs sont éphémères. Si tu écris des données dans le système de fichiers d'un conteneur puis que tu le supprimes, ces données disparaissent — l'image, elle, reste intacte. Pour persister quoi que ce soit, il faudra des volumes, sujet d'un chapitre dédié.
Le troisième concept, le registry, est un dépôt centralisé d'images — l'équivalent d'un npm ou d'un GitHub pour les images Docker. Le plus connu est Docker Hub, mais GitHub Container Registry est très utilisé, et tu peux héberger le tien en privé. C'est de là que Docker télécharge les images (postgres:16-alpine, node:22) et où tu publies les tiennes. Un dernier mécanisme mérite d'être connu dès maintenant car il explique la rapidité de Docker : les images sont construites en couches (layers), chaque instruction créant une couche mise en cache. Reconstruire une image sans rien changer est donc quasi instantané — et, tu le verras, l'ordre des instructions dans ta recette a un impact direct sur cette vitesse.
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
Vrai ou faux ?
Un conteneur Docker, c'est juste une machine virtuelle légère avec son propre noyau (kernel).
Vrai ou faux ?
Si je modifie un fichier à l'intérieur d'un conteneur en cours d'exécution puis que je supprime ce conteneur (sans volume), mes modifications sont perdues et l'image d'origine reste intacte.
Chapitre 3
Installer Docker
Selon ton OS, l'installation diffère. Voici les chemins recommandés en 2026.
L'installation de Docker diffère selon ton système, et le vocabulaire peut prêter à confusion entre « Docker », « Docker Desktop » et « Docker Engine ». Clarifions : le Docker Engine est le moteur qui fait réellement tourner les conteneurs ; Docker Desktop est une application tout-en-un qui empaquette ce moteur avec une interface graphique, Compose et d'autres outils, pour Mac et Windows. Sur Linux, on installe directement le moteur, sans interface graphique.
Un point important à connaître avant d'installer : la licence de Docker Desktop. Il est gratuit pour un usage personnel, étudiant et en petite entreprise, mais devient payant au-delà d'une certaine taille d'organisation. Si ce cadre te concerne, des alternatives gratuites et compatibles existent — OrbStack (souvent plus rapide sur Mac), Rancher Desktop ou Podman Desktop. Sur Linux, la question ne se pose pas : le Docker Engine s'installe librement, idéalement depuis le dépôt officiel pour avoir la dernière version stable plutôt que celle, souvent ancienne, fournie par ta distribution. Après l'installation sous Linux, pense à ajouter ton utilisateur·ice au groupe docker pour ne plus avoir à préfixer chaque commande par sudo. Enfin, quel que soit ton système, un docker run hello-world confirme que tout fonctionne de bout en bout.
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.
La ligne de commande (CLI) de Docker compte plus de cinquante commandes, de quoi intimider au premier abord. Bonne nouvelle : 90 % de ton usage quotidien tient en une dizaine d'entre elles. Plutôt que de tout mémoriser, concentre-toi sur ce socle, que ce chapitre couvre. Une fois ces réflexes acquis, tu iras chercher les commandes plus rares dans la documentation au moment où tu en auras besoin.
La commande centrale est docker run, qui télécharge l'image si besoin, crée un conteneur et le lance. Ce sont surtout ses options qui reviennent en permanence : -d pour lancer en arrière-plan, -p pour publier un port de la machine vers le conteneur, --name pour nommer le conteneur, -e pour passer une variable d'environnement, -v pour monter un volume, --rm pour supprimer automatiquement le conteneur à l'arrêt, et -it pour ouvrir un terminal interactif. Leur puissance se mesure sur un exemple concret : une seule commande docker run te donne un PostgreSQL prêt à l'emploi sur localhost:5432, sans aucune installation ni configuration système. C'est ce genre de gain qui rend Docker si populaire pour le développement.
Autour de docker run gravitent quelques familles de commandes à connaître. Pour le cycle de vie : docker ps liste les conteneurs, stop, start et rm les gèrent. Pour les images : docker images, pull, rmi et prune (nettoyage). Pour le débogage : docker logs affiche la sortie, docker exec -it <nom> bash ouvre un shell dans le conteneur pour inspecter de l'intérieur. Retiens surtout le réflexe de débogage numéro un : si un conteneur s'arrête aussitôt lancé avec -d, relance-le sans -d pour voir l'erreur directement dans ton terminal. Ce simple réflexe t'épargnera de longues minutes de tâtonnement.
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.
Jusqu'ici tu as utilisé des images faites par d'autres. Il est temps de construire les tiennes, et cela passe par le Dockerfile : un fichier texte qui décrit, étape par étape, comment fabriquer ton image. C'est la recette de ton environnement, et son grand mérite est d'être versionnée avec ton code — n'importe qui clonant ton dépôt peut reconstruire exactement le même environnement, ce qui referme définitivement le problème du « ça marche chez moi ».
Un Dockerfile s'écrit avec une poignée d'instructions, chacune créant une couche : FROM choisit l'image de base, WORKDIR définit le dossier de travail, COPY ajoute tes fichiers, RUN exécute des commandes pendant la construction (installer des dépendances), EXPOSE documente le port, et CMD définit la commande lancée au démarrage du conteneur. Un point mérite toute ton attention car il conditionne la vitesse de tes builds : l'ordre des instructions. Puisque chaque couche est mise en cache et qu'une modification invalide toutes les couches suivantes, on place ce qui change rarement en haut (l'installation des dépendances) et ce qui change souvent en bas (ton code applicatif). Bien ordonné, un rebuild ne réinstalle pas les dépendances à chaque changement de code — un gain de temps considérable au quotidien.
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.
Une vraie application dépasse vite le conteneur unique : une API, une base de données, un cache, parfois un reverse proxy. Lancer et coordonner chacun à la main avec de longues commandes docker run devient rapidement pénible et source d'erreurs. C'est exactement le problème que résout Docker Compose : décrire toute ta pile de services dans un unique fichier YAML, puis la démarrer d'une seule commande.
Le fichier docker-compose.yml déclare chaque service avec son image, ses ports, ses variables d'environnement, ses volumes et ses dépendances. Un simple docker compose up construit et lance l'ensemble ; docker compose down arrête et nettoie tout. Un bénéfice majeur, souvent découvert avec soulagement : Compose crée automatiquement un réseau privé où les services se joignent par leur nom. Ton API se connecte à la base via db:5432, pas via une adresse IP à retrouver — le nom du service fait office d'adresse. Deux détails pratiques à connaître : la clé version: en haut du fichier est désormais obsolète et peut être supprimée, et la commande moderne s'écrit docker compose (deux mots), l'ancienne docker-compose (avec tiret) étant en fin de vie.
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.
On revient sur la propriété la plus déroutante des conteneurs, celle qui cause le plus de mauvaises surprises chez les débutant·es : ils sont éphémères. Supprime un conteneur, et tout ce qu'il avait écrit dans son système de fichiers disparaît avec lui. Pour une application sans état (stateless), c'est parfait — on veut justement pouvoir jeter et recréer les conteneurs librement. Mais dès qu'il y a des données à conserver (une base, des fichiers uploadés), il faut les stocker ailleurs que dans le conteneur : c'est le rôle des volumes.
Docker propose deux mécanismes de stockage persistant, aux usages distincts. Le bind mount monte un dossier de ta machine directement dans le conteneur : idéal en développement pour que le conteneur voie tes modifications de code en temps réel, sans reconstruire l'image. Le named volume, lui, laisse Docker gérer le stockage dans sa propre zone : plus propre et plus portable, c'est le choix recommandé en production pour les données d'une base. La règle mentale à retenir : bind mount pour le code en dev, named volume pour les données en prod. Le scénario de ce chapitre te fait justement vivre la disparition de données Postgres faute de volume — l'erreur classique qu'on ne commet qu'une fois.
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.
🧭 Tes données Postgres ont disparu
Tu lances une base Postgres en local, tu insères des données, puis tu fais du ménage dans tes conteneurs. Au redémarrage, tout est vide. Que s'est-il passé, et que fais-tu ?
Ta base tournait avec docker run -d --name pg postgres:16-alpine, sans volume. Tu as fait docker rm -f pg puis relancé la même commande. Les tables sont vides. Quelle est la cause ?
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.
Faire tourner Docker en développement est facile ; le mettre en production proprement demande une poignée de réflexes supplémentaires. La différence entre une image bricolée et une image professionnelle tient à trois exigences : elle doit être légère, sûre et observable. Ce dernier chapitre rassemble les bonnes pratiques qui te feront passer du « ça tourne » au « ça tient en production ».
Côté légèreté, chaque mégaoctet compte : une image plus petite se transfère plus vite, démarre plus vite et offre moins de surface d'attaque. On part donc d'une base minimale (alpine, ou distroless qui n'a même pas de shell), on utilise un multi-stage build pour ne garder au final que le strict nécessaire à l'exécution (pas les outils de compilation), et on regroupe les commandes pour limiter les couches. Côté sécurité, le réflexe majeur est de ne jamais exécuter le conteneur en tant que root : on crée un utilisateur dédié avec l'instruction USER, si bien qu'une éventuelle faille dans l'application ne donne pas les pleins pouvoirs sur le conteneur. On évite aussi soigneusement d'inclure des secrets dans l'image.
Côté observabilité, enfin, un principe simple mais structurant : une application conteneurisée écrit ses journaux sur la sortie standard (stdout et stderr), pas dans un fichier à l'intérieur du conteneur. La raison est cohérente avec tout ce cours : le conteneur est éphémère, un fichier de log disparaîtrait avec lui. En écrivant sur la sortie standard, tu laisses la plateforme (Docker, Kubernetes, ton hébergeur) collecter et centraliser les logs. Ces trois familles de réflexes — image légère, exécution sans privilèges, logs sur la sortie standard — sont exactement ce qu'on attend d'une conteneurisation prête pour la production, et de bons arguments à mettre en avant en entretien ou dans un dossier de certification.
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
Pour aller plus loin
- Dockerfile reference (toutes les instructions) · docs.docker.com (en anglais) (nouvel onglet)
- Bonnes pratiques pour écrire un Dockerfile · docs.docker.com (en anglais) (nouvel onglet)
- Compose file reference · docs.docker.com (en anglais) (nouvel onglet)
- Gérer les données avec les volumes · docs.docker.com (en anglais) (nouvel onglet)
🛠️ 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.
🎓 Attestation
Ton attestation de réussite
Termine le QCM avec au moins 80% de bonnes réponses pour débloquer ton attestation de réussite.
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 →