Skip to Content

Chapitre 12.1 — Pourquoi Docker

⏱️ TL;DR — Docker règle un problème vieux comme le métier : « ça marche chez moi ». Au lieu d’installer Node, les libs et la config à la main sur chaque machine (et de prier pour qu’elles soient identiques), tu décris l’environnement une fois dans une image, et tu la fais tourner à l’identique partout — ton portable, la CI, le VPS. Un conteneur, ce n’est pas une machine virtuelle : il partage le noyau de l’hôte, donc il démarre en une seconde et pèse des Mo, pas des Go. Cinq mots à retenir : image (le gabarit figé), conteneur (une instance qui tourne), volume (les données qui survivent), réseau (comment les conteneurs se parlent), registre (là où on stocke et partage les images). Ce chapitre te les explique, puis installe Docker proprement sur Ubuntu.

🎯 Objectifs

  • Comprendre ce que la reproductibilité apporte, et pourquoi elle tue le « ça marche chez moi ».
  • Distinguer un conteneur d’une machine virtuelle (partage du noyau, poids, démarrage).
  • Définir les cinq concepts : image, conteneur, volume, réseau, registre.
  • Installer Docker Engine et le plugin compose sur Ubuntu, depuis le dépôt officiel.
  • Connaître le risque de sécurité du groupe docker (≈ root).
  • Manipuler les commandes de base : run, ps, images, logs, exec, stop, rm, pull.

Le mal que Docker soigne : « ça marche chez moi »

Tu connais la scène. L’app tourne parfaitement sur ton poste. Tu la déploies, et elle casse — une version de Node différente, une lib système absente, une variable d’environnement oubliée, un chemin qui n’existe pas sur le serveur. Le code est identique, mais l’environnement ne l’est pas. Et l’environnement, jusqu’ici, tu l’as posé à la main : apt install par-ci, une version de Node par-là, un fichier de config bricolé. Rien ne garantit qu’il soit le même d’une machine à l’autre.

Docker retourne le problème : au lieu de reconstruire l’environnement sur chaque machine en espérant tomber juste, tu le décris une fois dans un fichier texte (le Dockerfile), tu le construis en une image figée, et tu transportes cette image telle quelle. La machine qui la fait tourner n’a besoin que de Docker — pas de la bonne version de Node, pas des bonnes libs : tout est déjà dedans.

💡 Réflexe — Dès que tu te surprends à écrire un README du genre « installe Node 20, puis telle lib, puis exporte telle variable… », demande-toi si ce README ne devrait pas devenir un Dockerfile. Un environnement exécutable vaut mille pages d’instructions manuelles que personne ne suit à la lettre.

L’image devient la même vérité partout : le conteneur que tu testes sur ton portable est bit pour bit celui qui tournera en prod. Fini les divergences invisibles entre dev et prod — c’est la parité des environnements.

Conteneur ≠ machine virtuelle

C’est la confusion la plus fréquente. Une machine virtuelle (VM) émule un ordinateur complet : un hyperviseur fait tourner un système d’exploitation invité entier (son propre noyau Linux, ses services, son init) au-dessus de ton système hôte. Résultat : chaque VM pèse plusieurs Go, met des dizaines de secondes à démarrer, et réserve une part fixe de RAM.

Un conteneur, lui, ne réplique pas de système d’exploitation. Il partage le noyau de la machine hôte et n’embarque que ce qui est au-dessus : ton app, ses libs, ses fichiers. Le noyau Linux isole les conteneurs les uns des autres (via les namespaces et les cgroups) — chacun croit avoir sa propre machine — mais il n’y en a qu’un seul, partagé. D’où : démarrage en une fraction de seconde, poids en Mo, et beaucoup plus de conteneurs sur le même VPS que de VM.

⚠️ Piège — « Conteneur = petite VM » est un modèle mental faux qui te fera mal raisonner sur la sécurité et la persistance. Comme le noyau est partagé, l’isolation d’un conteneur est plus fine que celle d’une VM : un conteneur mal configuré (ou l’accès au socket Docker) peut compromettre l’hôte. Et comme le conteneur est jetable, tout ce que tu écris dedans disparaît quand tu le supprimes — sauf ce que tu ranges dans un volume (on y vient).

Les cinq concepts à posséder

1. L’image — le gabarit immuable

Une image est un modèle figé, en lecture seule, qui contient tout ce qu’il faut pour lancer ton app : le système de base minimal, le runtime (Node, PHP…), tes dépendances, ton code buildé, la commande de démarrage. Elle est construite par couches (layers) à partir d’un Dockerfile. Une fois construite, elle ne change plus : c’est un artefact qu’on versionne, qu’on tague (formacampus-web:1.4.0) et qu’on partage. Pense « classe » en POO.

2. Le conteneur — l’instance qui tourne

Un conteneur est une instance en cours d’exécution d’une image. Pense « objet » instancié depuis la classe. Tu peux lancer dix conteneurs à partir de la même image ; chacun est isolé, avec sa propre couche d’écriture éphémère par-dessus l’image en lecture seule. Le conteneur est jetable : on le tue, on le recrée, sans état à préserver — à condition d’avoir sorti les données dans un volume.

3. Le volume — les données qui persistent

Comme un conteneur est éphémère, tout ce qu’il écrit dans son système de fichiers meurt avec lui. Pour les données qui doivent survivre (la base Postgres, les fichiers uploadés), on utilise un volume : un espace de stockage géré par Docker, découplé du cycle de vie du conteneur. Tu détruis et recrées le conteneur Postgres autant que tu veux : tant que le volume est là, les données restent. C’est le concept que les débutants oublient — et le jour où ils recréent leur conteneur de base sans volume, ils perdent tout.

4. Le réseau — la communication entre conteneurs

Par défaut, Docker place tes conteneurs sur un réseau virtuel privé où ils se parlent par leur nom de service. Ton conteneur Next joint la base non pas via 127.0.0.1 mais via un nom comme db (le nom du service), que Docker résout en interne. C’est aussi le réseau qui décide de ce qui est exposé vers l’hôte (donc potentiellement vers Internet) et de ce qui reste strictement interne — un levier de sécurité central qu’on exploitera pour ne pas exposer Postgres.

5. Le registre — où vivent les images

Un registre est un entrepôt d’images. Docker Hub est le registre public par défaut (tu y pull node, postgres, nginx…). Il existe des registres privés : GHCR (GitHub Container Registry), celui de GitLab, ou ceux des fournisseurs cloud. Le cycle typique : tu construis ton image en CI, tu la pousses (push) dans un registre, puis le serveur de prod la tire (pull) et la lance. C’est le pont entre « je build » et « je déploie » qu’on exploitera en Partie 13.

🧭 Sur FormaCampus — Le problème « ça marche chez moi » était quotidien dans l’équipe : trois devs, trois versions de Node, une API Symfony qui exigeait des extensions PHP précises, et une base Postgres installée différemment sur chaque poste. Le plan : conteneuriser les trois briques — le front Next, l’API Symfony, Postgres — pour que dev, CI et prod partagent exactement le même environnement. Résultat visé : un nouveau dev clone le repo, tape une commande, et a la stack complète qui tourne en local, à l’identique de la prod. C’est le fil rouge de toute cette partie.

Installer Docker sur Ubuntu — proprement

Premier piège d’installation : le paquet docker.io fourni par Ubuntu est souvent en retard de plusieurs versions. On installe Docker Engine depuis le dépôt officiel Docker, qui inclut aussi le plugin compose v2 (la commande docker compose, en deux mots).

🐚 Au terminal — La séquence officielle : on ajoute la clé GPG et le dépôt Docker, puis on installe le moteur et ses plugins.

# 1. Dépendances pour récupérer le dépôt en HTTPS sudo apt update sudo apt install -y ca-certificates curl # 2. Ajouter la clé GPG officielle de Docker sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg \ -o /etc/apt/keyrings/docker.asc sudo chmod a+r /etc/apt/keyrings/docker.asc # 3. Ajouter le dépôt Docker aux sources APT (pour ta version d'Ubuntu) echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \ https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # 4. Installer le moteur + CLI + compose v2 sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io \ docker-buildx-plugin docker-compose-plugin # 5. Vérifier sudo docker run hello-world # doit télécharger l'image et afficher un message docker compose version # doit répondre v2.x

📚 La doc — Les commandes exactes (chemins de clés, noms de paquets) évoluent. La référence qui fait autorité est la page « Install Docker Engine on Ubuntu » de la doc officielle Docker : reproduis-la au moment de l’install plutôt que de recopier un vieux tutoriel. La commande docker compose version te confirme que c’est bien la v2 (jamais l’ancien binaire docker-compose en un mot).

Éviter de taper sudo partout — et le danger que ça cache

Par défaut, seul root (donc sudo) peut parler au démon Docker. Pour lancer docker sans sudo, on ajoute son utilisateur au groupe docker :

sudo usermod -aG docker deploy # ajoute 'deploy' au groupe docker # se déconnecter/reconnecter (ou 'newgrp docker') pour appliquer

🔒 Sécurité — Le groupe docker équivaut à root. Ce n’est pas une exagération : qui peut parler au démon Docker peut lancer un conteneur qui monte tout le disque de l’hôte et en prend le contrôle total. Mettre un utilisateur dans le groupe docker, c’est donc lui donner root sans mot de passe. Sur ton VPS FormaCampus, réserve ce groupe à l’utilisateur deploy (celui qui administre déjà), jamais à un compte applicatif ou à un utilisateur peu fiable. Et souviens-toi-en quand tu débogues des permissions : un membre du groupe docker a, de fait, les pleins pouvoirs.

Les commandes de base

Voici le kit de survie. Chaque commande fait une chose ; tu les combineras vite sans y penser.

# Lancer un conteneur à partir d'une image (la télécharge si absente) docker run -d --name web -p 8080:80 nginx # -d : en arrière-plan (detached) # --name : un nom lisible plutôt qu'un hash aléatoire # -p 8080:80: publie le port 80 du conteneur sur le 8080 de l'hôte # Lister les conteneurs qui tournent (ajoute -a pour voir aussi les arrêtés) docker ps docker ps -a # Lister les images présentes localement docker images # Voir les logs d'un conteneur (comme journalctl pour un service) docker logs web docker logs -f web # -f : suivre en direct (follow) # Ouvrir un shell DANS un conteneur qui tourne, pour inspecter docker exec -it web sh # -it : interactif + terminal ; 'sh' ou 'bash' # Arrêter, puis supprimer un conteneur docker stop web docker rm web # doit être arrêté avant (ou 'rm -f' pour forcer) # Télécharger une image sans la lancer (pré-charger une version) docker pull postgres:16

💡 Réflexedocker logs -f <conteneur> et docker exec -it <conteneur> sh sont tes deux outils de diagnostic au quotidien, exactement comme journalctl -u et un shell pour un service systemd. Quand un conteneur « ne marche pas », regarde ses logs d’abord : neuf fois sur dix, la cause y est écrite noir sur blanc (variable manquante, port déjà pris, permission refusée).

⚠️ Piègedocker run recrée un nouveau conteneur à chaque fois. Si tu enchaînes plusieurs docker run en croyant relancer le même, tu accumules des conteneurs fantômes (visibles avec docker ps -a) qui gardent des ports et de l’espace. Pour relancer un conteneur existant, c’est docker start <nom>, pas un nouveau run. Le compose du chapitre 12.3 te débarrassera de cette gymnastique.

✏️ Exercices

Exercice 1 — VM ou conteneur ? Un collègue veut « isoler » cinq petites apps Node sur un même VPS à 2 Go de RAM. Il hésite entre cinq machines virtuelles et cinq conteneurs. Que lui conseilles-tu, et sur quel argument technique ?

✅ Solution

Cinq conteneurs. Cinq VM voudraient dire cinq systèmes d’exploitation complets en mémoire, avec chacun son noyau et ses services : sur 2 Go, c’est intenable. Les conteneurs partagent le noyau de l’hôte et n’embarquent que l’app et ses libs — ils pèsent des Mo et démarrent en une fraction de seconde. Argument clé : le partage du noyau rend le conteneur radicalement plus léger que la VM pour ce besoin d’isolation d’apps sur une seule machine.

Exercice 2 — Le volume oublié. Tu lances Postgres dans un conteneur, tu crées des tables, tu insères des données. Puis tu fais docker rm -f sur le conteneur et tu le recrées à l’identique. Les données sont-elles là ? Pourquoi ? Que fallait-il faire ?

✅ Solution

Non, les données ont disparu. Tout ce qu’un conteneur écrit dans son propre système de fichiers vit dans sa couche d’écriture éphémère, détruite avec le conteneur. Pour que les données survivent, il fallait monter un volume (géré par Docker) sur le répertoire de données de Postgres. Le volume est découplé du cycle de vie du conteneur : on peut alors détruire et recréer le conteneur sans perdre la base. C’est le réflexe à ancrer avant de mettre une base en conteneur.

Exercice 3 — Installation propre. Un tutoriel te dit sudo apt install docker.io. Pourquoi préfères-tu une autre méthode sur un VPS de prod, et laquelle ?

✅ Solution

Le paquet docker.io de la distribution est souvent en retard de plusieurs versions et n’inclut pas forcément le plugin compose v2. Sur un serveur de prod, on installe Docker Engine depuis le dépôt officiel Docker (ajout de la clé GPG + du dépôt APT, puis docker-ce, docker-ce-cli, containerd.io, docker-buildx-plugin, docker-compose-plugin). On obtient ainsi une version à jour et la commande docker compose en deux mots. On vérifie avec docker compose version.

🧠 Quiz de révision

1. En une phrase : qu’est-ce que Docker résout ?

Le « ça marche chez moi » : Docker empaquette l’app avec son environnement dans une image reproductible qui tourne à l’identique en dev, en CI et en prod. C’est la parité des environnements qui élimine les divergences invisibles entre machines.

2. Quelle est la différence fondamentale entre un conteneur et une VM ?

Une VM émule un ordinateur complet avec un OS invité entier (son propre noyau) : lourde (Go), lente à démarrer. Un conteneur partage le noyau de l’hôte et n’embarque que l’app et ses libs : léger (Mo), démarre en une fraction de seconde. Le partage du noyau est la clé.

3. Distingue image et conteneur.

L’image est le gabarit figé (lecture seule), construit depuis un Dockerfile — pense « classe ». Le conteneur est une instance en cours d’exécution de cette image — pense « objet ». On lance plusieurs conteneurs à partir de la même image.

4. À quoi sert un volume, et que se passe-t-il sans lui ?

Un volume stocke les données de façon persistante, découplée du conteneur. Sans lui, tout ce que le conteneur écrit meurt à sa suppression — d’où la perte de données classique quand on recrée un conteneur de base sans volume.

5. Pourquoi le groupe docker est-il un enjeu de sécurité ?

Parce qu’il équivaut à root : un membre du groupe peut lancer un conteneur qui prend le contrôle total de l’hôte (montage du disque, etc.). On le réserve donc à l’utilisateur d’administration de confiance (deploy), jamais à un compte applicatif ou peu fiable.


Chapitre suivant : Dockerfile pour Next.js — écrire une image légère, multi-stage et non-root.

Last updated on