Prérequis¶
Connaissance de Linux (ligne de commande, arborescence, droits)
Notions de réseau TCP/IP (adresses IP, ports, protocoles)
Accès à un terminal avec droits administrateur (sudo)
Poste sous Linux, macOS ou Windows
Installation et architecture¶
01. Installation de Docker¶
Résultat attendu de docker run hello-world :
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
3. The Docker daemon created a new container from that image...
4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal.Comprendre la sortie de docker info :
docker info interroge le daemon et retourne l’état complet de l’installation. Sections clés à repérer :
Client:
Version: 26.1.4 <- version du client CLI
Context: default
Server:
Containers: 1 <- nombre total de conteneurs
Running: 0 <- conteneurs actifs
Paused: 0
Stopped: 1 <- conteneurs arrêtés (non supprimés)
Images: 1 <- images présentes localement
Server Version: 26.1.4 <- version du daemon
Storage Driver: overlay2 <- driver de système de fichiers en couches
Backing Filesystem: extfs <- système de fichiers de l'hôte
Logging Driver: json-file <- driver de logs par défaut
Cgroup Driver: systemd <- gestionnaire de Cgroups
Cgroup Version: 2 <- version des Cgroups (v2 recommandé)
Plugins:
Volume: local
Network: bridge host ipvlan macvlan null overlay
Runtimes: runc <- runtime OCI utilisé
containerd version: ...
runc version: ...
Operating System: Ubuntu 22.04.4 LTS
Architecture: x86_64
CPUs: 4 <- processeurs disponibles
Total Memory: 7.6GiB <- mémoire totale visible par Docker
Docker Root Dir: /var/lib/docker <- répertoire de stockage des données
Registry: https://index.docker.io/v1/ <- registry par défaut (Docker Hub)Points de vigilance :
Storage Driver: overlay2est le driver recommandé sur Linux —vfsindique un problème de configurationCgroup Version: 2est la version moderne ;1est progressivement abandonnéeDocker Root Dir: surveiller l’espace disque disponible sur cette partitionWarnings:en bas de la sortie signale des problèmes de configuration à corriger
02. Prise en main de Docker Desktop¶
Correspondance Desktop / CLI :
| Écran Docker Desktop | Commande CLI équivalente |
|---|---|
| Containers → liste | docker ps -a |
| Containers → Logs | docker logs NOM |
| Containers → Stats | docker stats NOM --no-stream |
| Containers → Terminal | docker exec -it NOM sh |
| Containers → Inspect | docker inspect NOM |
| Images → liste | docker images |
| Images → Pull | docker pull IMAGE:TAG |
| Volumes → liste | docker volume ls |
| Settings → Resources | docker info (CPUs, Total Memory) |
| Settings → Docker Engine | /etc/docker/daemon.json (Linux) |
Section Resources : les limites affichées (ex. 4 CPU, 8 Go RAM) correspondent aux ressources allouées à la VM Linux interne que Docker Desktop utilise sur Windows et macOS. Sur Linux natif, Docker accède directement au matériel — il n’y a pas de limite de ce type.
Section Docker Engine : le JSON affiché est le contenu de daemon.json. On peut y ajouter des paramètres comme un registry miroir ou un log driver spécifique.
03. Syntaxe et aide en ligne¶
docker --help affiche deux catégories de commandes :
Management Commands: <- commandes orientées objet (moderne)
container Manage containers
image Manage images
network Manage networks
volume Manage volumes
...
Commands: <- commandes historiques (toujours valides)
run Create and run a new container
ps List containers
images List images
rm Remove containers
rmi Remove images
...| Ancienne syntaxe | Syntaxe moderne |
|---|---|
docker ps | docker container ls |
docker ps -a | docker container ls -a |
docker images | docker image ls |
docker rm NOM | docker container rm NOM |
docker rmi IMAGE | docker image rm IMAGE |
docker inspect NOM | docker container inspect NOM |
Les deux syntaxes coexistent. La syntaxe moderne est préférée pour la lisibilité et la cohérence dans les scripts.
Options clés de docker run :
-d, --detach Détacher le conteneur (arrière-plan)
-it Mode interactif + TTY
--name string Nommer le conteneur
-p, --publish list Publier un port : HÔTE:CONTENEUR
-e, --env list Variable d'environnement
-v, --volume list Monter un volume
--network string Réseau à utiliser
--rm Supprimer le conteneur à l'arrêt
--restart string Politique de redémarrage
--memory string Limite mémoire (ex. 128m)
--cpus decimal Limite CPU (ex. 0.5)04. Limiter et surveiller les ressources¶
Étape 1 — docker inspect sans limite :
"Memory": 0,
"NanoCpus": 0,0 = pas de limite. Le conteneur peut utiliser toutes les ressources de l’hôte.
Étape 2 — docker inspect avec limites :
"Memory": 134217728,
"NanoCpus": 500000000,134217728octets = 128 Mo500000000nanoCPUs = 0,5 CPU
Étape 3 — Dépassement mémoire :
KilledLe processus est terminé par le kernel OOM Killer. docker inspect affichera "OOMKilled": true dans l’état du conteneur.
Étape 4 — docker update :
Les limites sont modifiables à chaud sans redémarrer le conteneur — utile pour absorber un pic de charge imprévu sans interruption de service.
Quand utiliser les limites de ressources :
Toujours en production sur un hôte multi-conteneurs
Quand plusieurs services partagent le même serveur (base de données, web, cache)
Pour protéger l’hôte contre un conteneur défaillant ou malveillant
En CI/CD pour éviter qu’un build runaway bloque les autres jobs
05. Isolation des processus et diagnostic¶
Solution to Exercise (Note) #
Étape 1 — PID depuis l’hôte :
root 3842 nginx: master process nginx -g daemon off;
nginx 3871 nginx: worker processPID depuis docker top :
UID PID PPID C STIME TTY TIME CMD
root 3842 3821 0 10:00 ? 00:00:00 nginx: master process nginx -g daemon off;
nginx 3871 3842 0 10:00 ? 00:00:00 nginx: worker processPID via /proc/1/status depuis l’intérieur :
Name: nginx
Pid: 1Depuis le conteneur, nginx est le PID 1. Depuis l’hôte (ps ou docker top), le même processus a un PID élevé (ex. 3842). C’est l’effet du Namespace PID.
docker top interroge le daemon Docker qui lit /proc sur l’hôte — il ne nécessite aucun outil installé dans le conteneur. cat /proc/1/status fonctionne même dans les images les plus minimales (distroless, scratch) car /proc est un système de fichiers virtuel du noyau, toujours présent.
Étape 2 — hostname :
# Hôte : mon-serveur
# Conteneur : a1b2c3d4e5f6 (ID court du conteneur)Personnalisable avec --hostname MON_NOM au lancement.
Étape 3 — interfaces réseau :
# Hôte : eth0, docker0 172.17.0.1, ...
# Conteneur : lo, eth0@if19 172.17.0.2/16Chaque conteneur a sa propre pile réseau via le Namespace net.
Étape 4 — nsenter :
# ip addr depuis le namespace réseau du conteneur
1: lo: <LOOPBACK,UP>
18: eth0@if19: <BROADCAST,UP> inet 172.17.0.2/16Quand utiliser nsenter :
Conteneur basé sur une image distroless ou scratch (pas de shell)
Conteneur bloqué qui ne répond plus au daemon Docker
Inspection réseau sans installer d’outils dans l’image de production
Audit de sécurité depuis l’hôte sans modifier le conteneur
Cgroups et Namespaces ensemble constituent l’isolation complète d’un conteneur. Docker ne réinvente pas l’isolation — il orchestre ces primitives Linux pour les rendre accessibles et portables.
Comparer VM et conteneurs¶
06. Empreinte mémoire et temps de démarrage¶
Durée estimée : 30 min
Étape 1 — Lancer un conteneur et mesurer la mémoire consommée
docker run -d --name nginx-demo nginx:alpine
docker stats nginx-demo --no-streamÉtape 2 — Mesurer le temps de démarrage
time docker run --rm alpine echo "Conteneur démarré"Étape 3 — Inspecter la configuration mémoire/CPU allouée
docker inspect nginx-demo | grep -E '"Memory"|"NanoCpus"|"CpuShares"'Étape 4 — Taille de l’image et des couches
docker image inspect nginx:alpine | grep -E '"Size"|"VirtualSize"'
docker history nginx:alpineÉtape 5 — Taille de la couche d’écriture du conteneur actif
docker ps --sizeÉtape 6 — Espace disque global occupé par Docker
docker system dfComparer avec l’empreinte d’une image VM Ubuntu : fichier .vmdk ou .qcow2 de 4 à 20 Go sur le disque.
Étape 7 — Lecture mémoire directement depuis le kernel (Linux uniquement)
# Récupérer le PID du processus nginx sur l'hôte
NGINX_PID=$(docker inspect --format '{{.State.Pid}}' nginx-demo)
echo "PID hôte : $NGINX_PID"
# Lire la mémoire résidente depuis /proc
grep VmRSS /proc/$NGINX_PID/statusÉtape 8 — Entrer dans le namespace du conteneur sans docker exec (Linux uniquement)
# Installer nsenter si absent
sudo apt-get install -y util-linux
# Entrer dans les namespaces PID et réseau du conteneur
sudo nsenter -t $NGINX_PID --pid --net -- ps aux
sudo nsenter -t $NGINX_PID --net -- ip addr showÉtape 9 — Lister tous les namespaces associés au conteneur
sudo ls -la /proc/$NGINX_PID/ns/Nettoyer
docker stop nginx-demo && docker rm nginx-demo
docker image prune -f
docker volume prune -fÉtape 1 — docker stats --no-stream :
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM %
a1b2c3d4e5f6 nginx-demo 0.00% 3.2MiB / 7.6GiB 0.04%Étape 2 — time docker run :
Conteneur démarré
real 0m0.412sDémarrage en moins d’une seconde. Une VM met 20 à 60 s.
Étape 3 — docker inspect (mémoire/CPU) :
"Memory": 0,
"NanoCpus": 0,
"CpuShares": 0,0 signifie aucune limite imposée — le conteneur peut utiliser toutes les ressources disponibles. Pour limiter : docker run --memory 128m --cpus 0.5 ...
Étape 4 — docker image inspect (taille) :
"Size": 43122160,
"VirtualSize": 43122160,Soit ~43 Mo. Une image VM Ubuntu de base occupe 2 à 4 Go.
Étape 5 — docker ps --size :
CONTAINER ID IMAGE SIZE
a1b2c3d4e5f6 nginx:alpine 2B (virtual 43.1MB)La couche d’écriture du conteneur actif (2 B ici) s’ajoute à la taille virtuelle de l’image de base, qui reste partagée en lecture seule entre tous les conteneurs issus de nginx:alpine.
Étape 6 — docker system df :
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 3 1 187MB 144MB (77%)
Containers 1 1 2B 0B
Local Volumes 0 0 0B 0B
Build Cache 0 0 0B 0BÉtape 7 — VmRSS depuis /proc :
VmRSS: 3284 kBMesure directe du noyau — cohérente avec ce que docker stats rapporte. Confirme que Docker ne fait pas de magie : c’est un processus Linux ordinaire, visible depuis l’hôte.
Étape 8 — nsenter :
# ps aux depuis le namespace PID du conteneur
PID USER COMMAND
1 root nginx: master process
29 nginx nginx: worker process
# ip addr depuis le namespace réseau du conteneur
1: lo: <LOOPBACK,UP>
18: eth0@if19: <BROADCAST,UP> inet 172.17.0.2/16nsenter entre directement dans les namespaces du conteneur sans passer par le daemon Docker. Utile pour diagnostiquer un conteneur dont l’image ne contient pas de shell.
Étape 9 — namespaces du processus :
lrwxrwxrwx 1 root root 0 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 root root 0 ipc -> ipc:[4026532345]
lrwxrwxrwx 1 root root 0 mnt -> mnt:[4026532343]
lrwxrwxrwx 1 root root 0 net -> net:[4026532348]
lrwxrwxrwx 1 root root 0 pid -> pid:[4026532346]
lrwxrwxrwx 1 root root 0 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 uts -> uts:[4026532344]Chaque namespace correspond à une dimension d’isolation : pid (processus), net (réseau), mnt (montages), ipc (IPC), uts (hostname), user (utilisateurs). Les numéros d’inode diffèrent de ceux de l’hôte — preuve de l’isolation effective.
| Machine virtuelle | Conteneur Docker | |
|---|---|---|
| Empreinte mémoire | 256 Mo – 2 Go | 3 – 50 Mo |
| Taille image disque | 2 – 20 Go | 5 – 200 Mo |
| Temps de démarrage | 20 – 60 s | < 1 s |
| Isolation | Hyperviseur + OS invité | Namespaces + Cgroups |
| Visibilité depuis l’hôte | Opaque (hyperviseur) | Processus visible dans /proc |
| Densité sur un hôte | 10 – 20 VM | 100+ conteneurs |
07. Coexistence de versions¶
Durée estimée : 10 min
Lancer trois versions de Node.js sur le même hôte sans conflit :
docker run --rm node:18-alpine node --version
docker run --rm node:20-alpine node --version
docker run --rm node:22-alpine node --versionVérifier qu’aucune version n’est installée nativement sur l’hôte :
node --version 2>/dev/null || echo "Node.js absent de l'hôte"Résultat attendu :
v18.20.4
v20.15.1
v22.3.0
Node.js absent de l'hôteSans Docker, faire coexister trois versions de Node.js nécessiterait nvm ou une gestion manuelle des chemins. Docker isole chaque version dans son propre conteneur — aucune interférence possible, aucune installation résiduelle sur l’hôte.
08. Déploiement rapide¶
Durée estimée : 15 min
Déployer une instance WordPress complète (web + base de données) en moins de 2 minutes :
docker network create wp-net
docker run -d \
--name wp-db \
--network wp-net \
-e MYSQL_ROOT_PASSWORD=rootpass \
-e MYSQL_DATABASE=wordpress \
-e MYSQL_USER=wp \
-e MYSQL_PASSWORD=wppass \
mariadb:10.11
docker run -d \
--name wp-web \
--network wp-net \
-p 8080:80 \
-e WORDPRESS_DB_HOST=wp-db \
-e WORDPRESS_DB_USER=wp \
-e WORDPRESS_DB_PASSWORD=wppass \
-e WORDPRESS_DB_NAME=wordpress \
wordpress:latestOuvrir http://localhost:8080 dans le navigateur.
Nettoyer
docker stop wp-web wp-db
docker rm wp-web wp-db
docker network rm wp-net
docker image prune -f
docker volume prune -fQuestion : combien de temps aurait pris le même déploiement sur des VM classiques ?
Sur des VM classiques : provisioning + installation OS + Apache + PHP + MySQL + WordPress = 30 min minimum, sans compter la configuration réseau entre les machines.
Avec Docker : docker run tire les images, démarre les conteneurs et les connecte en réseau en moins de 2 minutes, à l’identique sur n’importe quel hôte disposant de Docker.
Gestion des conteneurs et images¶
09. Cycle de vie d’un conteneur¶
Durée estimée : 30 min
Maîtriser le cycle de vie complet d’un conteneur — création, modification, sauvegarde de l’état, utilisation comme modèle.
Étape 1 — Démarrer un conteneur Nginx en mode détaché
docker run -d --name nginx-test nginx
docker psÉtape 2 — Démarrer un conteneur Ubuntu en mode interactif et le modifier
docker run -it --name ubuntu-test ubuntu /bin/bashDans le shell du conteneur :
apt-get update -qq && apt-get install -y curl
echo "modification" > /tmp/marqueur.txt
exitÉtape 3 — Sauvegarder l’état comme nouvelle image
docker commit ubuntu-test ubuntu-custom:1.0
docker images ubuntu-customÉtape 4 — Utiliser l’image comme modèle
docker run -it --name ubuntu-from-model ubuntu-custom:1.0 /bin/bash
cat /tmp/marqueur.txt
which curl
exitÉtape 5 — Renommer un conteneur
docker rename nginx-test nginx-prod
docker ps -aÉtape 6 — Nettoyer
docker stop nginx-prod && docker rm nginx-prod ubuntu-test ubuntu-from-model
docker rmi ubuntu-custom:1.0
docker image prune -f
docker volume prune -fRésultat attendu de docker images ubuntu-custom :
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu-custom 1.0 a1b2c3d4e5f6 30 seconds ago 132MBRésultat attendu dans le conteneur modèle (étape 4) :
modification
/usr/bin/curlL’état du conteneur d’origine est intégralement présent dans le nouveau conteneur.
docker commit est utile pour capturer un état de débogage. Pour un usage en production, préférer le Dockerfile : reproductible, versionnable, auditable.
10. Supervision des conteneurs¶
Durée estimée : 30 min
Utiliser les outils de supervision temps réel de Docker.
Étape 1 — Lancer un conteneur Nginx
docker run -d --name nginx-monitor -p 8080:80 nginxÉtape 2 — Consulter les logs
docker logs nginx-monitor
docker logs --follow nginx-monitorGénérer du trafic depuis un autre terminal :
curl http://localhost:8080Observer les nouvelles lignes en temps réel. Arrêter avec Ctrl+C.
Étape 3 — Surveiller les événements Docker
docker events &
docker stop nginx-monitor
docker start nginx-monitor
kill %1Étape 4 — Inspecter les processus actifs
docker top nginx-monitorÉtape 5 — Statistiques de performance
docker stats nginx-monitor --no-streamÉtape 6 — Nettoyer
docker stop nginx-monitor && docker rm nginx-monitor
docker image prune -f
docker volume prune -fRésultat attendu de docker logs après un curl :
172.17.0.1 - - [15/Jun/2026:10:00:00 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.88.1" "-"Résultat attendu de docker top :
UID PID PPID C STIME TTY TIME CMD
root 1234 1210 0 10:00 ? 00:00:00 nginx: master process nginx -g daemon off;
nginx 1265 1234 0 10:00 ? 00:00:00 nginx: worker processRésultat attendu de docker stats --no-stream :
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM %
a1b2c3d4e5f6 nginx-monitor 0.00% 6.5MiB / 7.6GiB 0.08%docker logs --follow est l’équivalent de tail -f. docker events fournit le flux brut des événements du daemon — utile pour diagnostiquer des redémarrages inattendus ou des erreurs réseau.
11. Variables d’environnement¶
Durée estimée : 30 min
Passer des paramètres de configuration à un conteneur sans modifier son image.
Étape 1 — Lancer MariaDB avec une variable inline
docker run -d \
--name mariadb-test \
-e MYSQL_ROOT_PASSWORD=MonMotDePasse123 \
mariadb:latest
docker ps
docker logs mariadb-testÉtape 2 — Se connecter à MariaDB
docker exec -it mariadb-test mariadb -u root -pMonMotDePasse123SHOW DATABASES;
EXIT;Étape 3 — Utiliser un fichier .env
Copier le fichier mariadb.env dans le répertoire de travail :
MYSQL_ROOT_PASSWORD=MotDePasseSecurise
MYSQL_DATABASE=mylab_docker
MYSQL_USER=admindb
MYSQL_PASSWORD=adminpass123
MYSQL_CHARACTER_SET_SERVER=utf8mb4
MYSQL_COLLATION_SERVER=utf8mb4_unicode_ci
mariadb.env
Arrêter le conteneur précédent et relancer avec le fichier :
docker stop mariadb-test && docker rm mariadb-test
docker run -d --name mariadb-env --env-file mariadb.env mariadb:latestÉtape 4 — Vérifier la base créée automatiquement
docker exec -it mariadb-env mariadb -u root -pMotDePasseSecurise -e "SHOW DATABASES;"Étape 5 — Nettoyer
docker stop mariadb-env && docker rm mariadb-env
rm mariadb.env
docker image prune -f
docker volume prune -fSolution to Exercise (mariadb.env) #
Résultat attendu de SHOW DATABASES; :
+--------------------+
| Database |
+--------------------+
| mylab_docker |
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+| Variable | Rôle |
|---|---|
MYSQL_ROOT_PASSWORD | Mot de passe root (obligatoire) |
MYSQL_DATABASE | Crée une base à l’initialisation |
MYSQL_USER / MYSQL_PASSWORD | Crée un utilisateur dédié |
Ne jamais committer un fichier .env contenant des mots de passe dans Git. Ajoutez *.env à votre .gitignore.
Création d’images¶
12. Construire une image avec Dockerfile¶
Durée estimée : 30 min
Rédiger un Dockerfile et construire une image Docker personnalisée.
Étape 1 — Préparer le répertoire de travail
mkdir mon-site && cd mon-siteCopier le fichier suivant dans le répertoire mon-site :
<!DOCTYPE html>
<html>
<head><title>Mon site Docker</title></head>
<body>
<h1>Bienvenue sur mon site conteneurisé !</h1>
<p>Conteneurisé avec Docker</p>
</body>
</html>
index.html
Étape 2 — Rédiger le Dockerfile
# Image de base : Nginx sur Alpine Linux (~5 Mo)
# Toutes les instructions suivantes s'appliquent au-dessus de cette image
FROM nginx:alpine
# Métadonnées informatives — sans effet sur l'exécution
LABEL maintainer="admin@mondomaine.fr"
LABEL version="1.0"
# Copie le fichier HTML depuis le contexte de build
# vers le répertoire racine servi par Nginx dans le conteneur
COPY index.html /usr/share/nginx/html/index.html
# Déclare le port d'écoute — informatif uniquement
# C'est l'option -p au docker run qui publie le port sur l'hôte
EXPOSE 80
# Commande exécutée au démarrage du conteneur
# daemon off : lance Nginx en avant-plan (requis pour que Docker détecte la fin du processus)
CMD ["nginx", "-g", "daemon off;"]
Dockerfile
Étape 3 — Construire et vérifier l’image
docker build -t mon-site:1.0 .
docker images mon-site
docker history mon-site:1.0Étape 4 — Lancer et tester
docker run -d --name site-test -p 8080:80 mon-site:1.0
curl http://localhost:8080Étape 5 — Publier l’image sur Docker Hub (optionnel — nécessite un compte)
Créer un compte sur hub.docker.com si ce n’est pas fait, puis créer un repository :
Se connecter sur hub.docker.com
Cliquer sur Create repository
Renseigner le nom
mon-site, choisir la visibilité (Public ou Private)Cliquer sur Create
Taguer l’image avec votre nom d’utilisateur Docker Hub et pousser :
# Remplacer VOTRE_COMPTE par votre identifiant Docker Hub
docker tag mon-site:1.0 VOTRE_COMPTE/mon-site:1.0
docker login
docker push VOTRE_COMPTE/mon-site:1.0Vérifier que l’image est visible sur https://hub.docker.com/r/VOTRE_COMPTE/mon-site.
Étape 6 — Nettoyer
docker stop site-test && docker rm site-test
cd .. && rm -rf mon-site
docker image prune -f
docker volume prune -fSolution to Exercise (index.html) #
Résultat attendu de docker history mon-site:1.0 :
IMAGE CREATED CREATED BY SIZE
a1b2c3d4e5f6 1 minute ago CMD ["nginx" "-g" "daemon off;"] 0B
<missing> 1 minute ago COPY index.html /usr/share/nginx/html/index.… 312B
<missing> 2 weeks ago EXPOSE map[80/tcp:{}] 0B
...Chaque instruction Dockerfile génère une couche. Les couches héritées de nginx:alpine sont partagées avec toutes les images qui en dérivent — aucune duplication sur le disque.
nginx:alpine pèse ~5 Mo contre ~140 Mo pour nginx:latest (base Debian). Préférer les images Alpine en production : surface d’attaque réduite, temps de pull divisé par 25.
13. Optimiser un Dockerfile¶
Durée estimée : 30 min
Réécrire un Dockerfile sous-optimal en appliquant les bonnes pratiques.
Étape 1 — Créer l’environnement de test
mkdir optim-dockerfile && cd optim-dockerfileCopier les fichiers suivants dans le répertoire optim-dockerfile :
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return 'Hello from Flask in Docker!'
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
app.py
Copier le Dockerfile initial à analyser :
FROM ubuntu:22.04
RUN apt-get update
RUN apt-get install -y python3
RUN apt-get install -y python3-pip
RUN pip3 install flask
RUN pip3 install requests
RUN apt-get install -y curl
RUN apt-get install -y vim
COPY app.py /app/app.py
WORKDIR /app
CMD ["python3", "app.py"]
Dockerfile
Construire et noter la taille :
docker build -t flask-avant:1.0 .
docker images flask-avant:1.0Étape 2 — Rédiger le Dockerfile optimisé
Remplacer le Dockerfile par la version optimisée :
FROM python:3.12-alpine
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
EXPOSE 5000
CMD ["python3", "app.py"]
Dockerfile
Ajouter le fichier de dépendances :
flask==3.0.0
requests==2.31.0
requirements.txt
Étape 3 — Lancer l’application avec montage du répertoire local
Plutôt que de reconstruire l’image à chaque modification, monter le répertoire local dans le conteneur permet d’éditer app.py directement depuis l’hôte et de voir les changements à chaud.
# Lancer le conteneur en montant le répertoire courant dans /app
docker run -d \
--name flask-dev \
-p 5000:5000 \
-v $(pwd):/app \
flask-apres:1.0
# Vérifier que l'application répond
curl http://localhost:5000Modifier app.py depuis l’hôte pour faire évoluer la réponse :
# Remplacer la ligne return dans hello() par :
return 'Hello from Flask — version 2 !'Redémarrer le processus Flask dans le conteneur pour prendre en compte la modification :
docker restart flask-dev
curl http://localhost:5000Le montage -v $(pwd):/app écrase le contenu copié par COPY app.py . dans l’image — c’est le fichier hôte qui est lu. Toute modification sur l’hôte est immédiatement disponible dans le conteneur après redémarrage du processus.
En développement Python, ajouter --reload (Werkzeug) ou utiliser flask run --reload évite le redémarrage manuel.
Arrêter le conteneur de développement :
docker stop flask-dev && docker rm flask-devÉtape 4 — Construire et comparer
docker build -t flask-apres:1.0 .
docker images | grep flaskÉtape 4 — Nettoyer
docker rmi flask-avant:1.0 flask-apres:1.0
cd .. && rm -rf optim-dockerfile
docker image prune -f
docker volume prune -fSolution to Exercise (app.py) #
Comparaison de tailles typique :
REPOSITORY TAG SIZE
flask-avant 1.0 512MB
flask-apres 1.0 71MB| Problème initial | Correction |
|---|---|
8 commandes RUN distinctes | 1 seule commande chaînée avec && |
| Image Ubuntu (77 Mo) | Image python:alpine (24 Mo) |
vim et curl inutiles en prod | Supprimés |
| Code copié avant dépendances | requirements.txt en premier (cache Docker) |
Cache Docker et ordre des instructions : en plaçant COPY requirements.txt avant COPY app.py, la couche pip n’est reconstruite que si les dépendances changent — pas à chaque modification du code source.
Exercice en autonomie - Choisir et configurer les images pour un site WordPress¶
Durée estimée : 30 min
L’objectif est de constituer la liste des images nécessaires au déploiement d’un site WordPress en load balancing derrière un Nginx, et de préparer la configuration sécurisée avant tout lancement.
Ce travail de sélection et de préparation précède systématiquement tout déploiement en production. Il n’y a pas de commande docker run à exécuter dans cet exercice : le résultat attendu est un répertoire de travail avec un fichier .env correctement rempli et un README.md justifiant les choix d’images.
Étape 1 — Sur hub.docker.com, rechercher les images officielles pour les trois composants de la pile : WordPress, MariaDB et Nginx. Pour chaque image, identifier les tags disponibles, distinguer les versions stables des versions de développement, et noter les variantes Alpine vs Debian.
Étape 2 — Pour chaque image retenue, consulter la page Docker Hub et identifier les variables d’environnement supportées. Recenser au minimum : les variables obligatoires (sans lesquelles le conteneur ne démarre pas), les variables optionnelles utiles pour la production, et les ports exposés par défaut.
Étape 3 — Créer un répertoire wp-projet et y créer un fichier .env rassemblant toutes les variables nécessaires : mots de passe MariaDB (root et utilisateur applicatif), nom de la base, nom d’utilisateur. Choisir des valeurs réalistes (pas password ni 123456).
Étape 4 — Ajouter un fichier .gitignore dans le répertoire pour exclure .env du contrôle de version. Vérifier que la règle est active.
Étape 5 — Rédiger un court README.md (une douzaine de lignes) justifiant les choix : version de chaque image, raison du choix de la variante (Alpine ou Debian), liste des variables configurées et leur rôle.
Étape 6 — Rédiger un court CHANGELOG.md (une douzaine de lignes) décrivant les modifications apportées : version de chaque image utilisée, justification du choix entre les variantes Alpine ou Debian, liste des variables d’environnement configurées et leur impact sur le fonctionnement de l’image.
Critères de validation
Le répertoire
wp-projetcontient.env,.gitignoreetREADME.md.envcontient au moins :MYSQL_ROOT_PASSWORD,MYSQL_DATABASE,MYSQL_USER,MYSQL_PASSWORD.gitignoreliste.envREADME.mdjustifie le choix de version pour chaque imageAucun mot de passe trivial (
password,123456,root) dans.env
Images recommandées pour la pile WordPress / load balancing
| Composant | Image | Tag recommandé | Variante | Raison |
|---|---|---|---|---|
| Base de données | mariadb | 10.11 | Debian | Version LTS, compatibilité garantie avec WordPress |
| Application | wordpress | 6.5-php8.3-apache | Apache | Plus simple à démarrer ; intègre Apache |
| Reverse proxy / LB | nginx | 1.27-alpine | Alpine | Image minimale ~5 Mo, surface d’attaque réduite |
Variables d’environnement de l’image mariadb :
| Variable | Obligatoire | Rôle |
|---|---|---|
MYSQL_ROOT_PASSWORD | Oui | Mot de passe administrateur |
MYSQL_DATABASE | Non | Crée une base au démarrage |
MYSQL_USER | Non | Crée un utilisateur applicatif |
MYSQL_PASSWORD | Non (requis si MYSQL_USER) | Mot de passe de l’utilisateur |
Variables d’environnement de l’image wordpress :
| Variable | Rôle |
|---|---|
WORDPRESS_DB_HOST | Nom ou IP du serveur MariaDB |
WORDPRESS_DB_NAME | Nom de la base de données |
WORDPRESS_DB_USER | Utilisateur applicatif |
WORDPRESS_DB_PASSWORD | Mot de passe |
WORDPRESS_TABLE_PREFIX | Préfixe des tables (optionnel, sécurité) |
Exemple de fichier .env :
# Base de données
MYSQL_ROOT_PASSWORD=R00t_S3cur3!2026
MYSQL_DATABASE=wordpress_prod
MYSQL_USER=wp_app
MYSQL_PASSWORD=Wp_Appl1c@tion!
# WordPress
WORDPRESS_TABLE_PREFIX=wpx_Exemple de fichier .env
Fichier .gitignore :
.env
*.env
*.env.*Règles de sécurité pour les fichiers .env :
Ne jamais committer un
.envcontenant des secrets dans Git, même dans un dépôt privéUn secret poussé par erreur reste dans l’historique même après suppression — il faut le révoquer
Utiliser des mots de passe d’au moins 16 caractères avec majuscules, chiffres et caractères spéciaux
Ne pas utiliser le même mot de passe pour
MYSQL_ROOT_PASSWORDetMYSQL_PASSWORDEn production, préférer un gestionnaire de secrets (Vault, Docker Secrets) plutôt qu’un fichier
.envVérifier les permissions :
chmod 600 .env(lecture propriétaire uniquement)
Pourquoi Alpine pour Nginx et Debian pour MariaDB / WordPress ?
Nginx en Alpine (~5 Mo) : surface d’attaque minimale, temps de pull rapide en CI/CD, parfaitement suffisant pour un reverse proxy sans extensions système.
MariaDB et WordPress en Debian : les extensions PHP et les drivers MySQL dépendent de bibliothèques système absentes d’Alpine ; la compatibilité est plus large et les images officielles sont mieux testées sur cette base.