Logo OFPPT

ISMO - Institut Spécialisé dans les Métiers de l'Offshoring

OFPPT - Office de la Formation Profesionnelle et de la Promotion du Travail

Docker Masterclass : Architecture, Sécurité et Haute Disponibilité

Dernière mise à jour : Janvier 2026

Parcours d'apprentissage

Partie 1 : Fondamentaux (Commencer par le Pourquoi)

Chapitre 1 : Comprendre le Besoin (VM vs Conteneurs)

1.1 Le Problème : "L'Enfer des Dépendances"

Avant Docker, déployer une application était un cauchemar. Le développeur codait sur son Mac avec Python 3.9, mais le serveur de production tournait sous Debian avec Python 3.6.

"Mais ça marche sur ma machine !" ¯\_(ツ)_/¯

Résultat : Des conflits de librairies, des versions différentes, et des heures perdues à debugger l'environnement plutôt que le code.

1.2 La 1ère Solution : La Machine Virtuelle (VM)

Pour isoler les applications, on utilisait des machines virtuelles (VMware, VirtualBox). Chaque application avait son propre ordinateur virtuel complet.

  • Avantage : Isolation totale.
  • Inconvénient majeur : Gaspillage énorme. Pour faire tourner une petite app Node.js de 10 Mo, on devait lancer un OS entier (Windows/Linux) qui prend 2 Go de RAM et 20 Go de disque.

1.3 La Révolution Docker : Le Conteneur

Docker a démocratisé le Conteneur. Au lieu de virtualiser le matériel (comme une VM), on partage le noyau du système hôte (Linux), mais on isole les processus.

🏢
Machine Virtuelle

Chaque locataire a sa PROPRE maison (fondations, murs, toit). Très cher, très lourd.

🏨
Conteneur

Tout le monde vit dans le MÊME immeuble (Même Noyau), mais chacun a sa chambre clé en main (Isolation).

1.4 Comment est-ce possible ? (L'Isolation Linux)

Docker n'invente rien, il utilise des briques Linux existantes pour créer cette illusion de chambre privée :

Les Namespaces (Les Murs)

C'est ce qui cache les voisins.
Ex: Le PID Namespace fait croire au conteneur qu'il est le seul processus (PID 1) sur la machine.

Les Cgroups (Les Gardiens)

C'est ce qui rationne la nourriture.
Ils empêchent un conteneur de manger toute la RAM ou le CPU au détriment des autres.

🛠️ Exercice de pensée

Ouvrez votre gestionnaire de tâches (si vous êtes sur Windows) ou `htop` (Linux).
Dans une VM, vous ne verriez pas les processus de l'invité.
Avec Docker, les processus des conteneurs SONT des processus normaux sur votre machine, juste "déguisés" et limités.

Chapitre 2 : Le système de fichiers UnionFS

Le Miracle de l'Héritage

Si je lance 100 conteneurs Ubuntu, est-ce que je copie 100 fois Windows ? Non. Docker utilise un système de calques (Layers).

  • Tous vos conteneurs partagent la même image de base en lecture seule (Le socle).
  • Chaque conteneur n'écrit que ses petites modifications sur une fine couche au-dessus.

Copy-on-Write (CoW)

Si vous ne touchez pas à un fichier, Docker lit l'original. Si vous le modifiez, Docker en crée une copie juste pour vous à la volée. C'est ultra-rapide et économise l'espace.

Chapitre 3 : Qui fait quoi ? (Architecture)

Composant Analogie Restaurant
Docker Client (CLI) Le client qui passe commande (Menu).
Docker Daemon Le Chef de salle qui prend la commande et gère la salle.
containerd Le Cuisinier en chef qui supervise la préparation.
runc Le commis qui assemble réellement le plat (le conteneur).

Partie 2 : Environnements et Contextes

Chapitre 1 : Architecture Docker sous Windows

1.1 Hyper-V vs WSL 2

Historiquement, Docker Desktop utilisait une grosse VM Hyper-V classique (lente au démarrage, allocation RAM fixe).
Aujourd'hui, WSL 2 (Windows Subsystem for Linux 2) est la norme :

  • Noyau Linux réel : Microsoft livre un vrai kernel Linux optimisé.
  • Dynamic RAM : La VM prend de la RAM quand elle en a besoin et la rend à Windows quand elle a fini.
  • Intégration Fichiers : Accès direct aux fichiers Linux depuis l'explorateur Windows (`\\wsl$\`).

Exercice : Vérifier son installation

Vérifions que votre moteur Docker est bien connecté à WSL 2 et fonctionnel. Exécutez ces commandes dans PowerShell ou Terminal.

# Vérifier la version Client et Serveur docker version # Vérifier le contexte actuel (sur quoi je suis connecté ?) docker context ls # Vérifier combien de conteneurs tournent docker ps

Si vous voyez une erreur "error during connect: ... Is the docker daemon running?", lancez Docker Desktop d'abord.
Dans la sortie de docker version, section "Server", vous devriez voir OS/Arch: linux/amd64. C'est la preuve que votre Docker tourne bien dans une VM Linux (WSL), même si vous êtes sous Windows.

Chapitre 2 : Interface Graphique vs Ligne de Commande

Le Dashboard (GUI)

Docker Desktop offre une interface visuelle très pratique.

  • Voir les logs en un clic.
  • Ouvrir un terminal dans le conteneur ("Exec").
  • Voir la consommation CPU/RAM visuelle.
  • Supprimer rapidement volumes et images.

Le CLI (Terminal)

La vraie puissance réside ici.

  • Automatisation via scripts bash.
  • Utilisation universelle sur serveurs Linux (qui n'ont pas d'écran).
  • Options avancées cachées dans l'interface graphique.

Chapitre 3 : Docker Contexts (Gérer le Multi-Environnement)

Par défaut, la commande `docker` parle au socket local `/var/run/docker.sock` (ou pipe Windows). Mais saviez-vous qu'elle peut piloter un Docker situé sur un serveur à l'autre bout du monde via SSH ?

Exercice Avancé : Simulation de Remote Docker

Nous allons créer un contexte. Disons que vous avez un serveur avec l'IP 192.168.1.55 et l'utilisateur admin.

# 1. Créer le contexte (ne fait rien pour l'instant) docker context create production-server \ --docker "host=ssh://admin@192.168.1.55" \ --description "Mon serveur de prod en France" # 2. Lister pour voir le nouveau contexte docker context ls # 3. Basculer dessus docker context use production-server # 4. Vérifier : cette commande liste les conteneurs DU SERVEUR DISTANT ! docker ps # 5. Revenir à la maison docker context use default

Pour que cela fonctionne, vous devez avoir configuré l'authentification SSH par clé (Key-based auth) sans mot de passe vers le serveur distant. Sinon, Docker demandera le mot de passe à chaque commande, ce qui cassera les scripts.

Partie 3 : Cycle de Vie et Manipulation Avancée

Chapitre 1 : Le Cycle de Vie d'un Conteneur

La Machine à États

∅ (Néant)
Created
Running
Paused
Exited
∅ (Deleted)

Pour passer d'un état à l'autre, on utilise les verbes d'action Docker :

  • create : Télécharge l'image et prépare le conteneur (ne lance pas le processus).
  • start : Lance le processus (PID 1). Passe à l'état Running.
  • run : Combo magique de create + start.
  • pause / unpause : Gèle le processus en mémoire (SIGSTOP).
  • stop : Arrêt gracieux (SIGTERM). Passe à l'état Exited.
  • kill : Arrêt brutal (SIGKILL).
  • rm : Supprime le conteneur Exited (libère le disque).

Exercice : Maîtriser le cycle de vie

Lancez un serveur web Nginx, vérifiez qu'il tourne, arrêtez-le et supprimez-le. Suivez les instructions.

# 1. Lancer Nginx en arrière-plan (détaché) docker run -d --name mon-web nginx # 2. Vérifier qu'il tourne docker ps # 3. Arrêter le conteneur docker stop mon-web # 4. Vérifier qu'il est bien arrêté (status Exited) docker ps -a # 5. Le supprimer docker rm mon-web

Vous avez la flemme de faire stop puis rm ?
Utilisez docker rm -f mon-web pour forcer la suppression d'un conteneur en cours d'exécution (envoie un SIGKILL). À utiliser avec précaution en prod !

Chapitre 2 : Récupération et Debugging Avancé

Cas d'école : Le conteneur qui crashe immédiatement

Vous lancez un conteneur et boum, il disparaît. docker ps ne le montre pas. La commande docker logs [id] est votre premier réflexe, mais parfois vous voulez voir les fichiers à l'intérieur pour comprendre (ex: un fichier de config corrompu). Mais comment entrer dans un conteneur arrêté ? (Spoiler: on ne peut pas faire docker exec sur un conteneur éteint).

La Solution : docker cp

L'outil cp marche même sur les conteneurs arrêtés ! Il permet d'extraire des fichiers "post-mortem".

Exercice Simulation de Panne

Nous allons créer un fichier dans un conteneur éphémère et essayer de le récupérer après sa mort.

# 1. Créer un conteneur qui écrit un secret et meurt aussitôt docker run --name cadavre alpine sh -c "echo 'Le code est 42' > /tmp/secret.txt" # 2. Vérifier qu'il est mort (Exited) docker ps -a | grep cadavre # 3. Essayer de récupérer le secret docker cp cadavre:/tmp/secret.txt ./mon-secret.txt # 4. Lire le fichier sur votre hôte cat mon-secret.txt

Le système de fichiers d'un conteneur persiste même après l'arrêt du processus. Tant que vous ne faites pas docker rm, toutes les données modifiées dans la couche inscriptible sont là, accessibles via docker cp ou docker export.

Chapitre 3 : Maintenance et Nettoyage (Prune)

La commande docker system prune est puissante mais dangereuse.

Commande Effet Dangerosité
docker container prune Supprime tous les conteneurs STOPPÉS. Faible
docker image prune Supprime les images "dangling" (sans tag, ). Faible
docker system prune -a Supprime TOUTES les images non utilisées par un conteneur actif. Élevée (Re-téléchargement nécessaire)
docker volume prune Supprime les volumes non attachés. Adieu les données ! CRITIQUE

Partie 4 : Dockerfile Expert

Chapitre 1 : Syntaxe Avancée (Best Practices)

  • WORKDIR vs RUN cd :
    Ne faites jamais RUN cd /app. Ça ne marche pas comme vous pensez car chaque RUN démarre un nouveau shell.
    Utilisez toujours WORKDIR /app. Si le dossier n'existe pas, il est créé.
  • ENV vs ARG :
    ARG (Build-time)
    Variables disponibles uniquement pendant la construction de l'image.
    Ex: ARG VERSION=1.0
    ENV (Run-time)
    Variables qui restent dans l'image finale et le conteneur.
    Ex: ENV NODE_ENV=production
  • Gestion du Cache : Docker met en cache chaque ligne. Ordonnez vos instructions du moins changeant au plus changeant. Copiez les package.json AVANT de copier le code source !

Exercice : Optimiser le Cache

Voici un Dockerfile mal optimisé. À chaque fois que vous changez une ligne de code, `npm install` se relance (long !). Comment le corriger ?

FROM node:18 WORKDIR /app # ❌ MAUVAISE PRATIQUE : Copie tout d'un coup COPY . . RUN npm install CMD ["node", "index.js"]
FROM node:18 WORKDIR /app # ✅ BONNE PRATIQUE : D'abord les dépendances COPY package.json package-lock.json ./ RUN npm install # Ensuite le code source (qui change souvent) COPY . . CMD ["node", "index.js"]

Pourquoi ? Si vous modifiez `index.js`, Docker voit que `package.json` n'a pas bougé. Il utilise donc le cache du layer `RUN npm install`. Build instantané !

Chapitre 2 : Le problème du PID 1 & Signaux

Le Piège du Shell Form

Il existe deux façons d'écrire CMD et ENTRYPOINT. Une seule est correcte pour la production.

❌ Shell Form CMD node app.js

Docker lance /bin/sh -c "node app.js". Le PID 1 est `sh`, pas `node`. Les signaux d'arrêt sont ignorés.

✅ Exec Form (JSON) CMD ["node", "app.js"]

L'exécutable est lancé directement. Il reçoit le SIGTERM proprement.

La Solution "Tini"

Parfois, votre application (ex: Java) ne sait pas gérer les signaux Docker même en Exec Form. La solution est d'utiliser un init process comme tini.

# Dans le Dockerfile RUN apk add --no-cache tini ENTRYPOINT ["/sbin/tini", "--"] CMD ["java", "-jar", "app.jar"]

Avec `docker run --init`, Docker injecte tini automatiquement sans modifier l'image.

Chapitre 3 : Multi-stage Build (Réduire la taille)

Pourquoi livrer les compilateurs (GCC, Maven, Go) en production ? Ils ne servent qu'au build.
Le Multi-stage build permet d'avoir une image temporaire de build, et de copier le résultat dans une image finale minuscule.

Exercice : Comprendre le Multi-stage

# ÉTAPE 1 : Builder (Image grosse avec tous les outils) FROM golang:1.19 AS builder WORKDIR /app COPY . . RUN go build -o mon-app main.go # ÉTAPE 2 : Runner (Image finale minuscule) FROM alpine:latest WORKDIR /root/ # La magie est ici : on copie SEULEMENT le binaire de l'étape précédente COPY --from=builder /app/mon-app . CMD ["./mon-app"]

Image Golang standard : ~800 Mo
Image Alpine finale : ~10 Mo
Gain : 98% !

Partie 5 : Persistance et Observabilité

Chapitre 1 : Stratégies de Stockage

Bind Mounts

📂 ↔️ 📦

Un chemin exact de l'hôte est monté dans le conteneur.

  • Dépendance : Dépend de l'OS et du chemin de l'hôte.
  • Usage : Développement (Hot reload).
  • Perf : Moins bonne sous Windows/Mac (Filesystem crossing).

Volumes Nommés

🐳 ➡️ 📦

Docker gère le stockage dans /var/lib/docker/volumes.

  • Dépendance : Indépendant de l'OS hôte.
  • Usage : Production (Databases).
  • Perf : Native (même sous WSL 2).

Exercice : Survivre à la suppression

Prouvons que les données survivent à la suppression du conteneur grâce aux volumes.

# 1. Créer un volume nommé docker volume create ma-base-bancaire # 2. Lancer un conteneur qui écrit dedans docker run --rm -v ma-base-bancaire:/data alpine sh -c "echo 'Solde: 1M€' > /data/compte.txt" # Le conteneur est détruit (--rm). Mais le volume ? # 3. Lancer un AUTRE conteneur qui monte le MEME volume docker run --rm -v ma-base-bancaire:/data alpine cat /data/compte.txt

Le second conteneur affiche Solde: 1M€. Vos données sont sauves !

Chapitre 2 : Maîtriser les Logs

Pourquoi la rotation est vitale ?

Par défaut, un conteneur qui tourne depuis 6 mois peut avoir un fichier JSON de logs de 50 Go. Si votre disque sature, tout le serveur plante.

La configuration se fait soit globalement (daemon.json) soit par conteneur.

Configuration Docker Compose

Copiez ce bloc dans tous vos services de production.

service: mon-app: image: mon-app:latest logging: driver: "json-file" options: max-size: "10m" # Un fichier fait max 10 Mo max-file: "3" # On garde max 3 fichiers (Rotation)

Résultat : Votre conteneur ne prendra jamais plus de 30 Mo de logs disque.

Partie 6 : Networking Avancé

Chapitre 1 : DNS et Communication Inter-Conteneurs

La magie du `127.0.0.11`

Chaque conteneur a dans son /etc/resolv.conf une entrée pointant vers un serveur DNS interne de Docker. C'est ce qui permet de faire ping mysql au lieu de ping 172.18.0.4.

Attention : Cette résolution magique ne marche PAS sur le réseau bridge par défaut. Vous DEVEZ créer vos propres réseaux.

Exercice : Le test du Ping

Prouvons la différence entre Bridge par défaut et User-defined bridge.

# 1. Créer un réseau perso docker network create mon-net # 2. Lancer deux conteneurs DANS ce réseau docker run -d --name c1 --net mon-net alpine sleep 3600 docker run -d --name c2 --net mon-net alpine sleep 3600 # 3. Tester le ping par nom (depuis c1 vers c2) docker exec c1 ping -c 2 c2 # 4. Essayer la même chose sur le bridge par défaut (sans --net) # Spoiler : Ça va échouer ("bad address 'c2'")

Sur le réseau mon-net, le ping répond ! Docker a automatiquement enregistré "c2" dans son DNS interne. C'est essentiel pour que votre API trouve sa base de données sans connaître son IP.

Chapitre 2 : Exposition des Ports

Port Mapping (-p)

-p HOSTE_PORT:CONTAINER_PORT

-p 8080:80 ouvre une brèche dans le réseau hôte. Tout le trafic arrivant sur le port 8080 de votre machine physique est redirigé vers le port 80 du conteneur.

Expose (Documentation)

EXPOSE 80

Sert uniquement pour la documentation et pour l'option -P (mappage aléatoire). N'ouvre rien techniquement.

Chapitre 3 : Isolation Réseau (Architecture 3-tiers)

En production, on n'expose pas tout à tout le monde.

Le Design Pattern "Backend Invisible"

# docker-compose.yml (extrait)

services:
  frontend:
    # Visible depuis Internet
    ports: ["80:80"]
    networks:
      - front-net

  api:
    # Visible seulement par le frontend
    expose: ["3000"] # Pas de ports mappés !
    networks:
      - front-net
      - back-net

  db:
    # Visible seulement par l'API (totalement isolée)
    networks:
      - back-net

networks:
  front-net:
  back-net:
    internal: true # Pas d'accès internet sortant (optionnel)

Partie 7 : Orchestration Locale avec Docker Compose

Chapitre 1 : L'Infrastructure as Code (IaC)

Docker Compose transforme vos lignes de commandes docker run interminables en un fichier de configuration élégant et versionné.

Exercice : Votre première stack LAMP

Objectif : Définir un serveur Web (Apache/PHP) et une base de données MySQL qui se parlent.

version: '3.8' services: website: image: php:8.1-apache ports: - "80:80" volumes: - ./src:/var/www/html environment: - DB_HOST=database # Nom du service ci-dessous database: image: mysql:5.7 environment: - MYSQL_ROOT_PASSWORD=root - MYSQL_DATABASE=myapp volumes: - db-data:/var/lib/mysql volumes: db-data:

docker-compose up -d : Démarre tout (crée réseau, volumes, conteneurs).
docker-compose ps : Vérifie l'état.
docker-compose logs -f : Suit les logs de tous les services mélangés (très pratique !).
docker-compose down : Tout éteindre et nettoyer (sauf les volumes).

Chapitre 2 : La course au démarrage (Race Conditions)

Le problème "Connection Refused"

Dans l'exercice précédent, PHP démarre en 100ms, mais MySQL met 5 à 10 secondes à s'initialiser. Si PHP tente de se connecter tout de suite, il plante.
Ajouter depends_on: [database] ne suffit pas ! (Ça attend juste que le conteneur soit créé, pas que MySQL soit prêt).

La Solution Robuste
services: web: depends_on: db: condition: service_healthy # Attend le OK du healthcheck db: image: postgres healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 10s timeout: 5s retries: 5

Chapitre 3 : Gestion Sécurisée des Variables (.env)

Ne commitez jamais vos mots de passe sur Git. Utilisez l'interpolation de variables.

Exercice : Sécuriser sa stack

Séparez la config du code.

# Fichier .env (à mettre dans .gitignore) DB_PASS=S3cr3tP@ssw0rd # docker-compose.yml services: db: image: mysql environment: - MYSQL_ROOT_PASSWORD=${DB_PASS}

Lancez docker-compose config.
Cette commande vous montre le YAML final généré. Vous verrez que Docker a bien remplacé ${DB_PASS} par la vraie valeur.