Module 1 — Docker avancé : builds multi-étapes et non-root
Bonne lecture et bon apprentissage ! Junior TSAFACK – 12/09/2026 Temps de lecture estimé : 9 minutes
Ce module suppose une familiarité de base avec Docker (déjà vue dans le cours d’installation de Podman, l’équivalent rootless de Docker). L’accent est mis ici sur deux pratiques qui séparent une image « qui marche » d’une image « prête pour la production » : le build multi-étapes et l’utilisateur non-root.
L’application de démonstration
Section titled “L’application de démonstration”Une petite API Flask, avec un compteur de visites stocké dans Redis (pour démontrer, au module suivant, la communication entre conteneurs) :
import osimport redisfrom flask import Flask, jsonify
app = Flask(__name__)r = redis.Redis(host=os.environ.get("REDIS_HOST", "redis"), port=6379, decode_responses=True)
@app.route("/")def index(): count = r.incr("visites") return jsonify(message="Bonjour depuis le conteneur applicatif", visites=count)
@app.route("/health")def health(): return jsonify(status="ok")Version naïve (à éviter)
Section titled “Version naïve (à éviter)”FROM python:3.12WORKDIR /appCOPY requirements.txt .RUN pip install --no-cache-dir -r requirements.txtCOPY app.py .CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]Version multi-étapes et non-root
Section titled “Version multi-étapes et non-root”FROM python:3.12-slim AS builderWORKDIR /buildCOPY requirements.txt .RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
FROM python:3.12-slimWORKDIR /app
RUN groupadd -r appuser && useradd -r -g appuser appuser
COPY --from=builder /install /usr/localCOPY app.py .
USER appuser
EXPOSE 5000CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]Ce que le multi-stage change concrètement : la première étape (builder) installe les dépendances avec tous les outils de compilation nécessaires (souvent volumineux) ; la seconde étape repart d’une image propre et ne récupère, via COPY --from=builder, que le résultat de l’installation (/usr/local) — pas les outils de build eux-mêmes. Le conteneur final ne contient jamais les compilateurs, en-têtes de développement, ou caches de build utilisés pour le fabriquer.
Les deux images, construites et comparées réellement
Section titled “Les deux images, construites et comparées réellement”$ docker images --format "{{.Repository}}:{{.Tag}} {{.Size}}"docker-avance-app-naive:latest 1.63GBdocker-avance-app:latest 202MBUn facteur 8 sur la taille, avec exactement le même code applicatif. Une image plus petite, c’est un déploiement plus rapide (moins à télécharger sur chaque nœud), une surface d’attaque réduite (moins de binaires présents = moins de vulnérabilités potentielles), et des scans de sécurité (voir le module Sécurité & Conformité) plus rapides et plus faciles à trier.
$ docker run --rm docker-avance-app-naive whoamiroot
$ docker run --rm docker-avance-app whoamiappuserPourquoi c’est important, concrètement : un conteneur qui tourne en root et qui serait compromis (par exemple via une vulnérabilité de la bibliothèque flask ou d’une de ses dépendances) donne à l’attaquant les pleins pouvoirs à l’intérieur du conteneur — et selon la configuration du moteur de conteneurs, potentiellement un chemin d’escalade vers l’hôte. Faire tourner le processus applicatif en tant qu’utilisateur non privilégié (USER appuser) limite ce que cette compromission peut accomplir, même si le conteneur lui-même reste vulnérable — c’est le principe du moindre privilège, appliqué au niveau du conteneur.
Orchestrer deux conteneurs avec Docker Compose
Section titled “Orchestrer deux conteneurs avec Docker Compose”services: app: build: . ports: - "5001:5000" environment: - REDIS_HOST=redis depends_on: - redis
redis: image: redis:7-alpine restart: unless-stoppedDémarré réellement :
$ docker compose up -d Container docker-avance-redis-1 Started Container docker-avance-app-1 Started
$ docker compose psNAME STATUS PORTSdocker-avance-app-1 Up 3 seconds 0.0.0.0:5001->5000/tcpdocker-avance-redis-1 Up 4 seconds
$ curl -s http://localhost:5001/{"message":"Bonjour depuis le conteneur applicatif","visites":1}$ curl -s http://localhost:5001/{"message":"Bonjour depuis le conteneur applicatif","visites":2}$ curl -s http://localhost:5001/{"message":"Bonjour depuis le conteneur applicatif","visites":3}Le compteur s’incrémente correctement à chaque requête : les deux conteneurs communiquent bien via le réseau Docker créé automatiquement par Compose (l’application résout redis — le nom du service, pas une adresse IP codée en dur — grâce au DNS interne de ce réseau).
Prochaine étape
Section titled “Prochaine étape”Module 2 : Kubernetes — déployer une vraie application sur un cluster réel