CoursB2-23 - Conteneurs en production : Docker, Compose, Podman
Cours · Fiche de cours
B2-23 - Conteneurs en production : Docker, Compose, Podman
Sommaire
B2-23 Conteneurs en production : Docker, Compose, Podman
🎓 SIO2 · semestre 3⏱️ 5 h (cours + TP)🎯 Bloc 2 – SISRComprendre ce qu'est un conteneur, manipuler images, volumes et réseaux, décrire une application complète avec Compose, la publier derrière Traefik et l'exploiter en sécurité, avec Docker ou Podman.
🎯 Objectifs
Expliquer la différence entre un conteneur et une machine virtuelle, et choisir l'un ou l'autre.
Décrire une image : couches, tag, registre ; écrire un Dockerfile simple.
Lancer, observer et dépanner un conteneur : docker run, ps, logs, exec.
Garder les données avec des volumes et isoler les services avec des réseaux.
Décrire une application web et sa base de données dans un fichier Compose.
Publier l'application derrière Traefik configuré par fichiers, puis l'exploiter en sécurité (Docker ou Podman).
📋 Prérequis
Administrer une Debian : paquets, services, journaux.B2-14 · Debian 13
Configurer Traefik avec des fichiers dynamiques.B2-12 · Traefik
Test express des prérequis 3 questions · 2 minutes · conseillé avant de commencer
1. Sur Debian 13, quelle commande affiche les dernières lignes du journal du service nginx ?
journalctl -u filtre le journal de systemd par service, -n 20 limite aux 20 dernières lignes. À revoir : B2-14 · Debian 13.
2. Dans Traefik, quel élément indique l'adresse réelle de l'application vers laquelle transmettre la requête ?
Le routeur choisit la requête, les middlewares la modifient, le service sait où est l'application (url: "http://127.0.0.1:8080"). À revoir : B2-12 · Traefik.
3. Dans Proxmox VE, qu'est-ce qui distingue un conteneur LXC d'une machine virtuelle ?
Un conteneur partage le noyau de l'hôte : il est léger, mais ne peut faire tourner que des systèmes Linux. À revoir : B2-21 · Proxmox VE.
Brocéliande Logistique (entreprise fictive) veut un site d'actualités internes pour ses 60 salariés : notes de la direction, plannings de l'entrepôt, informations de l'agence de Rennes. Le prestataire propose WordPress, avec une base MariaDB. L'an dernier, une application semblable installée à la main sur un serveur avait cassé lors d'une mise à jour de PHP, et personne ne savait plus comment elle avait été configurée.
Le responsable informatique veut cette fois une installation décrite dans un fichier, que l'on peut relancer, mettre à jour ou reconstruire en quelques minutes. Il te confie le déploiement de l'application en conteneurs, sur la VM Debian, publiée en HTTPS derrière le reverse proxy Traefik déjà en place, avec des données qui survivent à tout.
Le coursConteneur et VM, images et registres, premiers conteneurs, volumes et réseaux, Compose, publication derrière Traefik, sécurité en production, puis Podman et le dépannage.
1. Conteneur ou machine virtuelle ?
Une machine virtuelle simule un ordinateur complet : l'hyperviseur lui donne un processeur, de la mémoire et un disque virtuels, et elle démarre son propre système, avec son propre noyau. Un conteneur, lui, n'est qu'un groupe de processus isolés qui tournent directement sur le système hôte et partagent son noyau Linux. L'isolement est assuré par deux mécanismes du noyau : les namespaces, qui cloisonnent ce que le conteneur voit, et les cgroups, qui limitent ce qu'il consomme.
Clique sur chaque technologie pour voir ce qui est empilé sur le serveur.
Choisis une technologie pour comparer.
La VM virtualise le matériel ; le conteneur isole des processus sur un noyau partagé.
Pour bien comprendre : maison et appartements
Une VM ressemble à une maison individuelle : elle a ses propres fondations, sa chaudière, son compteur. Elle est très indépendante, mais longue à construire et coûteuse en terrain.
Les conteneurs sont des appartements dans un immeuble : chacun a sa porte fermée à clé et son aménagement, mais tous reposent sur les mêmes fondations (le noyau de l'hôte). On emménage en quelques secondes et on loge beaucoup plus de monde sur le même terrain.
Revers de la médaille : si les fondations ont un défaut, tout l'immeuble est concerné. Une faille du noyau touche tous les conteneurs de l'hôte.
Machine virtuelle
Conteneur
Ce qui est virtualisé
Le matériel : la VM démarre son propre noyau
Le système : processus isolés sur le noyau de l'hôte
Taille, démarrage
Quelques Go, dizaines de secondes
Quelques dizaines de Mo, environ une seconde
Systèmes possibles
Tous (Linux, Windows, BSD…)
Applications Linux sur un hôte Linux
Isolement
Très fort (frontière matérielle)
Bon, mais noyau partagé
Usage typique
Contrôleur de domaine, pare-feu, serveur complet
Applications web, API, bases de données, outils
En entreprise, on ne choisit pas l'un contre l'autre : les conteneurs tournent presque toujours dans des VM. La VM apporte l'isolement fort et la sauvegarde par l'hyperviseur, les conteneurs apportent la légèreté et la reproductibilité des applications. C'est exactement l'architecture du TP : une VM Debian dans Proxmox, et des conteneurs dedans.
Docker a popularisé les conteneurs d'application (un service par conteneur) et le format d'image, aujourd'hui normalisé par l'OCI. Podman, porté par Red Hat, lance les mêmes images sans démon (partie 9). Les conteneurs LXC de Proxmox sont des conteneurs système : ils ressemblent à une petite VM Linux complète, avec ses services.
2. Images, couches et registres
Un conteneur est toujours lancé à partir d'une image : un modèle en lecture seule qui contient l'application, ses bibliothèques et la commande à exécuter au démarrage. Une image n'est pas un gros fichier unique, mais un empilement de couches : chaque couche ne contient que les fichiers ajoutés ou modifiés par rapport à la précédente.
Les couches en lecture seule sont partagées ; seule la couche d'écriture appartient au conteneur.
2.1 Lire le nom complet d'une image
Le nom docker.io/library/mariadb:11.4 se décompose en quatre morceaux, souvent abrégés : mariadb:11.4 suffit, car Docker Hub et l'organisation library (images officielles) sont pris par défaut.
docker.ioLe registre, serveur qui stocke et distribue les images : Docker Hub (docker.io), GitHub (ghcr.io), Red Hat (quay.io) ou un registre privé de l'entreprise (Harbor, GitLab).
library/mariadbLe dépôt : organisation puis nom. library regroupe les images officielles, maintenues et analysées régulièrement.
:11.4Le tag, une étiquette de version choisie par l'éditeur. Il peut être déplacé : 11.4 pointe toujours vers la dernière correction de la 11.4.
@sha256:…Le digest, empreinte du contenu exact. Contrairement au tag, il ne change jamais : on l'utilise pour figer une version de façon certaine.
latest n'est pas « la dernière version stable » : c'est seulement le tag utilisé quand on n'en précise aucun. Un docker pull peut donc faire passer une base de données à une version majeure sans prévenir. En production, on précise toujours un tag de version (mariadb:11.4), et on décide quand on change.
2.2 Construire sa propre image : le Dockerfile
Un Dockerfile décrit la construction d'une image. Le plus souvent, on utilise des images toutes faites ; on écrit un Dockerfile pour empaqueter une application maison.
FROML'image de départ. On choisit une image officielle et légère (-slim, -alpine) : moins de fichiers, moins de failles.
RUNUne commande exécutée pendant la construction ; elle crée une couche. On enchaîne avec && et on nettoie dans la même ligne pour garder la couche petite.
COPYCopie des fichiers du projet dans l'image.
EXPOSEDocumente le port écouté par l'application. N'ouvre rien : la publication se fait au lancement (-p).
CMDLa commande lancée au démarrage du conteneur. Elle doit rester au premier plan : quand elle s'arrête, le conteneur s'arrête.
Debian 13 construire et inspecter l'image
$ sudo docker build -t broceliande/suivi:1.0 . # le point = dossier qui contient le Dockerfile $ sudo docker image ls # REPOSITORY broceliande/suivi TAG 1.0 SIZE 125MB $ sudo docker image history broceliande/suivi:1.0 # une ligne par couche
3. Installer Docker et lancer un premier conteneur
3.1 Installation sur Debian 13
Debian fournit un paquet docker.io, mais en production on installe le plus souvent Docker Engine depuis le dépôt officiel de Docker, plus à jour, avec le greffon Compose. On ajoute la clé de signature du dépôt, puis le dépôt lui-même :
Le démon dockerd tourne en root et reçoit les ordres de la commande docker par un socket, /var/run/docker.sock. Sans droits sur ce socket, la commande répond permission denied while trying to connect to the Docker daemon socket : on la lance avec sudo. On verra en partie 8 pourquoi ajouter un utilisateur au groupe docker n'est pas anodin.
3.2 Les commandes de base
Debian 13 un serveur web en une ligne
$ sudo docker run -d --name web -p 127.0.0.1:8080:80 --restart unless-stopped nginx:1.28-alpine $ sudo docker ps # NAMES web · STATUS Up 5 seconds · PORTS 127.0.0.1:8080->80/tcp $ curl -s http://127.0.0.1:8080 | grep title # <title>Welcome to nginx!</title> $ sudo docker logs -f web # journal du conteneur (sortie de nginx), Ctrl+C pour quitter $ sudo docker exec -it web sh # un shell DANS le conteneur ; exit pour sortir $ sudo docker stop web && sudo docker rm web # arrêter puis supprimer
-dDétaché : le conteneur tourne en arrière-plan et la commande rend la main.
--name webUn nom choisi, plus pratique que l'identifiant aléatoire pour logs, exec, stop.
-p 127.0.0.1:8080:80Publication de port : adresse de l'hôte : port de l'hôte : port du conteneur. Sans adresse, le port est ouvert sur toutes les interfaces de l'hôte.
--restart unless-stoppedRedémarre le conteneur après un plantage ou un redémarrage de la machine, sauf si on l'a arrêté volontairement.
nginx:1.28-alpineL'image et son tag, toujours en dernier ; ce qui suit l'image remplace la commande CMD. Vérifie sur le registre la version stable du moment.
3.3 La vie d'un conteneur
Un conteneur arrêté existe encore (on peut lire ses journaux) ; supprimé, sa couche d'écriture est perdue.
Un conteneur ne vit que tant que son processus principal tourne. S'il s'arrête aussitôt démarré, docker ps -a le montre en Exited avec un code de sortie, et docker logs donne presque toujours la raison : variable obligatoire manquante, port déjà pris, fichier de configuration invalide.
4. Garder les données : volumes et montages
Un conteneur est jetable : pour le mettre à jour, on ne le modifie pas, on le supprime et on en recrée un depuis la nouvelle image. Tout ce qu'il a écrit dans sa couche d'écriture disparaît alors. Les données qui doivent durer (base de données, fichiers envoyés par les utilisateurs, configuration) sont donc rangées en dehors du conteneur, dans un volume monté à l'intérieur.
Règle d'or : rien d'important dans la couche d'écriture d'un conteneur.
Volume nommé
Montage d'un dossier (bind mount)
Syntaxe
-v db_data:/var/lib/mysql
-v /srv/site:/usr/share/nginx/html:ro
Où sont les fichiers ?
Gérés par le moteur : /var/lib/docker/volumes/db_data/_data
Dans le dossier de l'hôte que tu choisis
Usage
Données d'une application : bases, fichiers envoyés
Fichiers que l'administrateur écrit : configuration, site statique, certificats
Bon réflexe
Un volume par donnée à conserver, avec un nom parlant
:ro (lecture seule) dès que le conteneur n'a pas à écrire
Debian 13 manipuler les volumes
$ sudo docker volume create db_data $ sudo docker run -d --name db -e MARIADB_ROOT_PASSWORD=Ch4ng3-m0i -v db_data:/var/lib/mysql mariadb:11.4 $ sudo docker volume ls # DRIVER local · VOLUME NAME db_data $ sudo docker volume inspect db_data # "Mountpoint": "/var/lib/docker/volumes/db_data/_data" $ sudo docker rm -f db && sudo docker run -d --name db -e MARIADB_ROOT_PASSWORD=Ch4ng3-m0i -v db_data:/var/lib/mysql mariadb:11.4 # les bases sont toujours là
Copier le dossier d'un volume pendant que la base tourne donne une sauvegarde incohérente. Pour une base, on fait un export logique depuis le conteneur : docker exec db mariadb-dump … (TP, étape 7). Les volumes de fichiers se copient quand l'application est arrêtée, ou se sauvegardent avec la VM entière par Proxmox Backup Server.
5. Les réseaux des conteneurs
Chaque conteneur reçoit une interface réseau et une adresse privée (par défaut dans 172.17.0.0/16), reliée à un bridge sur l'hôte. Deux règles organisent les échanges :
Entre conteneurs : sur un réseau créé par l'administrateur (ou par Compose), un DNS interne résout le nom de chaque conteneur. WordPress joint la base par le nom db, jamais par une adresse qui change à chaque recréation. Sur le réseau bridge par défaut, ce DNS n'existe pas.
Depuis l'extérieur : un conteneur n'est joignable que par les ports publiés (-p). Une base de données ne se publie jamais : seuls les conteneurs de son réseau y accèdent.
Un seul chemin d'entrée : Traefik, puis le port web publié sur la boucle locale. La base n'est joignable que par web.
Debian 13 réseaux
$ sudo docker network ls # bridge, host, none + les réseaux créés $ sudo docker network create back $ sudo docker run -d --name db --network back -e MARIADB_ROOT_PASSWORD=Ch4ng3-m0i mariadb:11.4 $ sudo docker run --rm --network back debian:13-slim getent hosts db # 172.18.0.2 db : le DNS interne répond $ sudo docker network inspect back # sous-réseau, passerelle et conteneurs reliés
Pour publier un port, Docker écrit ses propres règles de NAT dans le noyau, avant celles de ton pare-feu (nftables, ufw). Un -p 3306:3306 rend donc la base joignable depuis tout le réseau, même si ton pare-feu « bloque » le 3306. Publie le strict nécessaire, et sur 127.0.0.1 quand un reverse proxy local est devant.
6. Docker Compose : décrire une application complète
Avec docker run, une application à deux conteneurs demande deux longues commandes, deux volumes et un réseau à créer dans le bon ordre. Compose décrit tout cela dans un seul fichier YAML, compose.yaml, que l'on range dans un dossier par application (par exemple /opt/actus) et que l'on versionne dans Git. Une seule commande crée ou met à jour l'ensemble.
Fichier /opt/actus/compose.yaml
name: actus
services:
db:
image: mariadb:11.4
restart: unless-stopped
environment:
MARIADB_DATABASE: wordpress
MARIADB_USER: wp
MARIADB_PASSWORD: ${DB_PASSWORD} # lu dans le fichier .env
MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
volumes:
- db_data:/var/lib/mysql
networks: [back]
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
retries: 5
web:
image: wordpress:7-apache
restart: unless-stopped
depends_on:
db:
condition: service_healthy # attend que la base soit prête
environment:
WORDPRESS_DB_HOST: db # le nom du service, pas localhost
WORDPRESS_DB_USER: wp
WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}
WORDPRESS_DB_NAME: wordpress
volumes:
- wp_data:/var/www/html
ports:
- "127.0.0.1:8081:80" # visible seulement depuis l'hôte (Traefik)
networks: [front, back]
volumes:
db_data:
wp_data:
networks:
front:
back:
internal: true # aucun accès vers ou depuis l'extérieur
servicesUn bloc par conteneur. Le nom du service (db, web) sert aussi de nom DNS sur les réseaux du projet.
image · restartL'image avec un tag de version précis, et la politique de redémarrage (après un plantage ou un redémarrage de la VM).
environment · ${…}Variables passées à l'application. Les secrets sont lus dans un fichier .env placé à côté, protégé (chmod 600) et jamais versionné.
volumesMontages du service, et en bas du fichier la déclaration des volumes nommés. Compose les préfixe avec le nom du projet : actus_db_data.
depends_on · healthcheckOrdre de démarrage. Avec condition: service_healthy, web ne démarre que lorsque le test de santé de db réussit, et pas seulement quand son conteneur existe.
ports · networksPublication du seul port utile, sur la boucle locale ; deux réseaux : front pour sortir, back interne pour la base.
Le fichier décrit l'état voulu ; Compose crée ou corrige ce qui manque, dans le bon ordre.
Commande (dans le dossier du projet)
Effet
docker compose config
Vérifie le fichier et affiche la configuration finale, variables remplacées
docker compose up -d
Crée ou met à jour tout le projet, en arrière-plan
docker compose ps
État des services : running, healthy, ports publiés
docker compose logs -f web
Journaux d'un service en continu
docker compose exec db bash
Shell dans un service en marche
docker compose pull puis up -d
Mise à jour : télécharge les nouvelles images et recrée les conteneurs concernés
docker compose down
Arrête et supprime conteneurs et réseaux ; garde les volumes
docker compose down supprime les conteneurs et les réseaux, mais garde les volumes : on relance avec up -d et tout revient. Avec down -v, les volumes du projet sont supprimés aussi : base et fichiers perdus, sans confirmation. À réserver au labo, après une sauvegarde.
7. Publier l'application derrière Traefik
Les utilisateurs ne doivent pas taper http://172.16.50.11:8081 : ils ouvrent https://actus.broceliande.lan. C'est le rôle du reverse proxy Traefik vu en B2-12 : il reçoit les connexions HTTPS, présente le certificat, ajoute les en-têtes de sécurité et transmet à l'application. Comme pour tous les services de Brocéliande, on le configure uniquement par des fichiers dynamiques (file provider) : un fichier par service publié.
Traefik ne sait rien des conteneurs : il connaît une adresse et un port, comme pour n'importe quelle application.
YAML /etc/traefik/dynamic/actus.ymlappliqué à chaud
http:
routers:
actus:
rule: "Host(`actus.broceliande.lan`)"
entryPoints: [websecure]
service: actus
middlewares: [securite] # défini dans intranet.yml (B2-12)
tls: {}
services:
actus:
loadBalancer:
servers:
- url: "http://127.0.0.1:8081" # le port publié par Compose
tls:
certificates:
- certFile: /etc/traefik/certs/actus.crt
keyFile: /etc/traefik/certs/actus.key
Tous les fichiers du dossier forment une seule configuration : un middleware déclaré dans intranet.yml est utilisable dans actus.yml. WordPress sait qu'il est servi en HTTPS grâce à l'en-tête X-Forwarded-Proto: https que Traefik ajoute.
7.1 Variante : Traefik lui-même en conteneur
Si Traefik n'est pas installé sur l'hôte, on peut l'ajouter comme service du projet, toujours avec le file provider. Il rejoint le réseau front et joint l'application par son nom de service : le port de web n'a alors plus besoin d'être publié.
Traefik sait aussi découvrir les conteneurs tout seul (provider Docker) : on décrit alors la publication dans des labels de chaque conteneur. Pour cela, il faut lui donner le socket /var/run/docker.sock, c'est-à-dire le contrôle total du moteur, donc de l'hôte : une faille de Traefik deviendrait une prise de contrôle du serveur. Les fichiers dynamiques évitent ce risque, regroupent toute la publication au même endroit, se relisent et se versionnent. C'est la règle au labo et dans cette fiche.
8. Sécuriser des conteneurs en production
Un conteneur isole une application, mais il ne la rend pas sûre : il partage le noyau de l'hôte, il tourne souvent en root à l'intérieur, et son image peut contenir des failles connues. La sécurité se joue à quatre niveaux : qui pilote le moteur, d'où viennent les images, comment chaque service est lancé, et comment on l'entretient.
8.1 Qui pilote le moteur ?
Le démon Docker tourne en root. Toute personne qui peut lui parler (membre du groupe docker, ou programme qui a accès à /var/run/docker.sock) peut lancer un conteneur qui monte la racine de l'hôte, et devenir root sur la machine en une ligne :
Debian 13 démonstration au labo · compte stagiaire membre du groupe docker
stagiaire$ id # uid=1001(stagiaire) groups=1001(stagiaire),989(docker) stagiaire$ docker run --rm -it -v /:/hote debian:13-slim chroot /hote bash root# cat /etc/shadow # lisible : c'est le fichier de l'hôte, et on est root
Ajouter un compte au groupe docker, c'est lui donner les droits de root, sans mot de passe et sans trace dans les journaux de sudo. On réserve le moteur aux administrateurs, on passe par sudo docker (tracé dans le journal), et on ne monte jamaisdocker.sock dans un conteneur. Pour aller plus loin : le mode rootless de Docker, ou Podman (partie 9), où le moteur tourne avec les droits d'un simple utilisateur.
8.2 Des images de confiance et à jour
Origine : images officielles (library/) ou d'éditeurs vérifiés ; pour une image maison, un Dockerfile relu, construit à partir d'une image officielle.
Version : un tag de version précis, jamais latest en production ; on lit les notes de version avant de changer de version majeure.
Failles connues : on analyse les images avec un scanner, par exemple trivy image wordpress:7-apache ou docker scout cves wordpress:7-apache, qui liste les CVE par gravité.
Mises à jour : un conteneur ne se met pas à jour avec apt upgrade à l'intérieur (perdu à la recréation). On télécharge la nouvelle image et on recrée : docker compose pull && docker compose up -d, puis docker image prune pour supprimer les anciennes.
8.3 Lancer chaque service avec le minimum de droits
Dans compose.yaml
Effet
security_opt: ["no-new-privileges:true"]
Aucun processus du conteneur ne peut gagner de droits (programmes setuid, sudo)
cap_drop: [ALL] puis cap_add
Retire toutes les capacités de root, puis rend seulement celles que l'image exige (à tester service par service)
user: "1000:1000"
Lance le processus avec un compte non privilégié, quand l'image le permet
read_only: true + tmpfs: [/tmp]
Système de fichiers du conteneur en lecture seule : un attaquant ne peut pas y déposer d'outil
mem_limit: 512m · cpus: 1.0
Plafonne la mémoire et le processeur : un service emballé ne bloque pas les autres
À proscrire : privileged: true, network_mode: host, /var/run/docker.sock monté
Chacun supprime une grande partie de l'isolement
8.4 Secrets et journaux
Les mots de passe ne s'écrivent pas dans compose.yaml : ils vont dans .env (droits 600, listé dans .gitignore), ou mieux dans des secrets Compose, des fichiers montés dans /run/secrets/ que lisent les images officielles grâce aux variables …_FILE (MARIADB_PASSWORD_FILE, WORDPRESS_DB_PASSWORD_FILE). Côté journaux, Docker garde par défaut toute la sortie de chaque conteneur dans un fichier qui grossit sans limite : on fixe une rotation pour tout l'hôte.
Podman lance les mêmes images OCI avec presque les mêmes commandes, mais sans démon central : chaque conteneur est un processus lancé directement par l'utilisateur qui tape la commande, et supervisé par systemd. Par défaut, il fonctionne en rootless : le root d'un conteneur n'est qu'un utilisateur ordinaire sur l'hôte. C'est l'outil par défaut de Red Hat, AlmaLinux et Rocky Linux, et il est disponible dans Debian 13.
Même format d'image, même registre ; ce qui change, c'est qui exécute les conteneurs et avec quels droits.
Debian 13 Podman en simple utilisateur
$ sudo apt install -y podman podman-compose $ podman run -d --name test -p 127.0.0.1:8082:80 docker.io/library/nginx:1.28-alpine # sans sudo $ podman ps && podman logs test # mêmes verbes que docker $ podman exec -it test sh $ podman top test user huser # root dans le conteneur = ton UID sur l'hôte $ podman compose up -d # lit le même compose.yaml (via podman-compose)
Pour qu'un conteneur Podman démarre avec la machine, on le confie à systemd avec un fichier Quadlet : une unité .container que systemd transforme en service.
$ systemctl --user daemon-reload && systemctl --user start test $ sudo loginctl enable-linger $USER # les services de l'utilisateur tournent même sans session ouverte
Docker
Podman
Architecture
Un démon root, piloté par un socket
Pas de démon : chaque conteneur est un processus supervisé
Droits par défaut
Root (mode rootless possible)
Rootless ; ports inférieurs à 1024 interdits sans réglage
Multi-conteneurs
Docker Compose
Pods, podman compose, Quadlet
Démarrage avec la machine
restart: géré par le démon
systemd (Quadlet)
Noms d'images
nginx = Docker Hub par défaut
Nom complet conseillé : docker.io/library/nginx
Les images et les fichiers Compose sont les mêmes. Docker reste le plus répandu et le mieux documenté ; Podman apporte un meilleur cloisonnement (rootless, pas de socket root) et une intégration native à systemd. Au labo, on travaille avec Docker ; sache transposer en Podman, souvent en changeant seulement le nom de la commande.
10. Dépanner une application conteneurisée
10.1 La boîte à outils
Question
Commande
Les services tournent-ils ? Sont-ils sains ?
docker compose ps -a · docker inspect --format '{{.State.Health.Status}}' actus-db-1
Pourquoi s'est-il arrêté ?
docker compose logs --tail 50 db (et le code de sortie dans ps -a)
Quelle configuration a-t-il reçue ?
docker compose config · docker compose exec web env
Quel port est publié, et où ?
docker compose port web 80 · ss -tlnp sur l'hôte
Les conteneurs se voient-ils ?
docker network inspect actus_back · docker compose exec web getent hosts db
Reste-t-il de la place ?
docker system df · df -h /var/lib/docker
10.2 Le chemin de diagnostic
https://actus.broceliande.lan ne s'affiche pas Réponds en partant de l'application, puis remonte vers le client
« docker compose ps » montre-t-il web en marche et db « healthy » ?Lance la commande dans /opt/actus.
Sur la VM, « curl -I http://127.0.0.1:8081 » répond-il (200 ou 302) ?On teste l'application sans passer par Traefik.
Sur la VM, « curl -kI --resolve actus.broceliande.lan:443:127.0.0.1 https://actus.broceliande.lan » répond-il ?On teste Traefik sans dépendre du DNS du poste.
Lis docker compose logs db puis web. Causes fréquentes : fichier .env absent (variables vides), mot de passe différent de celui de la première initialisation du volume, port déjà utilisé, test de santé en échec. Corrige, puis docker compose up -d.
Le conteneur tourne mais ne répond pas sur ce port : vérifie la ligne ports et docker compose port web 80, le port réellement écouté par l'application (80 pour l'image WordPress Apache), puis docker compose logs web.
Le problème est dans Traefik : sudo journalctl -u traefik -n 30. Vérifie le nom et l'extension du fichier dans /etc/traefik/dynamic/, la règle Host(`…`) avec ses accents graves, l'url du service (127.0.0.1:8081) et les chemins du certificat.
Le serveur fonctionne : le problème est côté client. Vérifie la résolution du nom (nslookup actus.broceliande.lan, enregistrement DNS ou fichier hosts), le pare-feu de la VM sur le port 443, puis le cache du navigateur.
10.3 Entraîne-toi : les tickets du support
L'application d'actualités est en service depuis un mois. Six tickets arrivent : lis le message, observe le relevé et choisis la cause.
File d'attente du support6 tickets · un à la fois · l'explication s'affiche après ta réponse
Ticket n° 1De : directionPriorité haute
« Depuis la mise à jour de la base faite par le stagiaire, le site affiche la page d'installation de WordPress. Tous nos articles ont disparu ! »
Debian 13 history (extrait) · serveur de test
docker run -d --name db --network back -e MARIADB_ROOT_PASSWORD=… mariadb:11.4
docker rm -f db
docker run -d --name db --network back -e MARIADB_ROOT_PASSWORD=… mariadb:11.8
Quelle est la cause ?
Cause : sans -v, /var/lib/mysql était dans la couche d'écriture ; docker rm l'a effacée. Correction : restaurer la dernière sauvegarde dans un conteneur qui monte un volume nommé (-v db_data:/var/lib/mysql), et décrire le service dans un fichier Compose pour que l'erreur ne se reproduise pas.
Ticket n° 2De : technicienPriorité haute
« J'ai écrit mon compose.yaml pour une deuxième instance de test. Le site affiche "Error establishing a database connection", alors que db est healthy. »
Cause : chaque conteneur a sa propre pile réseau ; localhost dans web, c'est web. Correction :WORDPRESS_DB_HOST: db, le nom du service, résolu par le DNS interne du réseau du projet. Publier 3306 est inutile et dangereux.
Ticket n° 3De : technicienPriorité moyenne
« Mon conteneur web refuse de démarrer sur SRV-LINUX, alors qu'il marchait sur mon poste. »
Debian 13 docker compose up -d
Error response from daemon: driver failed programming external
connectivity on endpoint actus-web-1: failed to bind host port
0.0.0.0:80/tcp: address already in use
Quelle est la cause ?
Cause :ports: "80:80" demande le port 80 de toutes les interfaces, où Traefik écoute déjà. Correction : publier sur un port libre de la boucle locale ("127.0.0.1:8081:80") et laisser Traefik recevoir le trafic. sudo ss -tlnp | grep :80 montre qui occupe un port.
Cause : quelqu'un a ajouté ports: "3306:3306" à db pour « tester ». Docker a ouvert le port sur toutes les interfaces, en contournant les règles d'entrée de l'hôte. Correction : supprimer cette ligne (web joint db par le réseau interne) et recréer avec docker compose up -d ; pour administrer la base, utiliser docker compose exec db mariadb.
« https://actus.broceliande.lan affiche "Bad Gateway". Le technicien dit que les conteneurs tournent. »
Debian 13 journalctl -u traefik (extrait)
level=error msg="502 Bad Gateway" error="dial tcp: lookup web on
172.16.50.10:53: no such host" routerName=actus@file
Quelle est la cause ?
Cause : l'URL http://web:80 ne convient qu'à un Traefik placé dans le même réseau Docker (variante 7.1). Un Traefik installé sur l'hôte interroge le DNS de l'entreprise, qui ignore ce nom. Correction :url: "http://127.0.0.1:8081" dans actus.yml ; Traefik l'applique à chaud.
Ticket n° 6De : chargée de communicationPriorité basse
« Quand je me connecte à l'administration du site, je suis renvoyée vers http://127.0.0.1:8081/wp-admin, qui ne s'ouvre pas. »
MariaDB [wordpress]> SELECT option_name, option_value FROM wp_options
-> WHERE option_name IN ('siteurl','home');
siteurl http://127.0.0.1:8081
home http://127.0.0.1:8081
Quelle est la cause ?
Cause : WordPress mémorise l'adresse utilisée lors de l'installation. Correction :UPDATE wp_options SET option_value='https://actus.broceliande.lan' WHERE option_name IN ('siteurl','home');. Bonne pratique : faire l'installation directement par l'adresse publique, derrière Traefik.
11. À l'examen et en entreprise
Documentation attendue : le schéma de l'application (conteneurs, réseaux, volumes, port publié, reverse proxy), le compose.yaml commenté, le fichier dynamique Traefik et la procédure de mise à jour et de sauvegarde. Les mots de passe n'apparaissent jamais dans le dossier.
Démonstration : docker compose down puis up -d devant le jury, et montrer que les articles sont toujours là, est très parlant. Montrer aussi que http://IP-VM:8081 ne répond pas depuis le poste.
Justifier ses choix : conteneur ou VM, volume plutôt que couche du conteneur, port sur la boucle locale, Traefik en fichiers plutôt qu'en étiquettes, Docker ou Podman. Le jury demande souvent comment on met à jour l'application sans perte.
En entreprise : un dossier par application dans /opt, versionné dans Git (sans .env), des images à version fixée, une mise à jour planifiée et testée, et une sauvegarde de la base exportée chaque nuit.
12. L'essentiel
Un conteneur est un groupe de processus isolés (namespaces, cgroups) qui partage le noyau de l'hôte ; une VM a son propre noyau.
Une image est un empilement de couches en lecture seule ; le conteneur ajoute une couche d'écriture, perdue à sa suppression.
Nom d'image : registre/dépôt:tag ; en production, un tag de version précis, jamais latest.
docker run -d --name … -p … -v … image:tag, puis ps, logs, exec, stop, rm.
Les données à garder vont dans un volume nommé ; la configuration de l'administrateur dans un montage en :ro.
Sur un réseau créé par Compose, les services se joignent par leur nom ; localhost désigne le conteneur lui-même.
Publier le minimum, sur 127.0.0.1 derrière un reverse proxy : un port publié contourne le pare-feu de l'hôte.
Compose décrit l'application dans compose.yaml + .env ; up -d crée ou met à jour, down -v efface les volumes.
Publication par Traefik en fichiers dynamiques : jamais d'étiquettes ni de docker.sock monté.
Sécurité : groupe docker = root, images officielles analysées, no-new-privileges, mises à jour par pull + up -d ; Podman offre le mode rootless.
La fiche en une image : à refaire de mémoire, sur une feuille, avant de passer aux exercices.
La pratiqueLe quiz noté pour vérifier tes acquis, les exercices à rendre à ton professeur, puis le TP au labo pour publier une application web et sa base de données avec Compose.
13. Quiz noté
8 questions · 10 minutesRéponds à toutes les questions, puis valide. Seule ta première tentative est enregistrée : elle compte dans ton suivi. Ensuite, tu peux t'entraîner autant que tu veux.
1. Qu'est-ce qu'un conteneur partage avec le système hôte ?
2. Un conteneur a écrit des fichiers sans volume. Que deviennent-ils après docker rm ?
3. Que fait l'option -p 127.0.0.1:8081:80 ?
4. Dans un même projet Compose, comment le service web doit-il joindre la base du service db ?
5. Pourquoi faut-il éviter le tag latest en production ?
6. Quel est l'effet de docker compose down -v ?
7. Pourquoi publie-t-on les conteneurs avec Traefik configuré en fichiers dynamiques plutôt qu'avec des étiquettes Docker ?
8. Quelle différence principale distingue Podman de Docker ?
14. Exercices : à toi de répondre
Écris ta réponse sous chaque énoncé et envoie-la à ton professeur. Quand il l'a validée, le corrigé s'affiche à la place du cadenas ; s'il te la renvoie « à revoir », lis son commentaire et corrige.
Exercice 1 – Comprendre (SIO2)
Pour chacun de ces besoins de Brocéliande Logistique, choisis entre VM et conteneur et justifie en une phrase : a) un second contrôleur de domaine Windows Server ; b) trois instances de test de l'application d'actualités ; c) le pare-feu IPFire de l'agence de Rennes ; d) un outil de supervision web (Uptime Kuma). Explique ensuite pourquoi dix conteneurs lancés depuis la même image n'occupent pas dix fois la taille de l'image.
Le corrigé s'affichera ici dès que ton professeur aura validé ta réponse. Cherche d'abord par toi-même : c'est en se trompant qu'on retient.
Exercice 2 – Configurer (SIO2)
Écris le fichier /opt/supervision/compose.yaml qui déploie Uptime Kuma (image louislam/uptime-kuma:2, port interne 3001, données dans /app/data) : redémarrage automatique, données dans un volume nommé, port publié seulement pour un Traefik installé sur l'hôte, sur le port 8082. Écris ensuite le fichier dynamique Traefik qui publie l'outil sous https://supervision.broceliande.lan.
Le corrigé s'affichera ici dès que ton professeur aura validé ta réponse. Cherche d'abord par toi-même : c'est en se trompant qu'on retient.
Exercice 3 – Dépanner (SIO2)
Un technicien propose ce fichier pour un wiki interne. Relève au moins cinq problèmes (fonctionnement ou sécurité) et propose la correction de chacun.
Le corrigé s'affichera ici dès que ton professeur aura validé ta réponse. Cherche d'abord par toi-même : c'est en se trompant qu'on retient.
Exercice 4 – Concevoir (SIO2)
Brocéliande Logistique veut héberger en conteneurs quatre applications internes (actualités, wiki, supervision, gestion des clés). Propose une organisation de production complète : nombre et rôle des VM, Docker ou Podman, rangement des fichiers, publication, sauvegardes, mises à jour, sécurité et documentation. Justifie en une quinzaine de lignes.
Le corrigé s'affichera ici dès que ton professeur aura validé ta réponse. Cherche d'abord par toi-même : c'est en se trompant qu'on retient.
15. Au labo : publier une application web et sa base avec Compose
Objectif
Sur la VM Debian 13 de ton pool Proxmox (page Mes serveurs et VM), tu déploies le site d'actualités de Brocéliande Logistique : WordPress et MariaDB décrits dans un fichier Compose, publiés en HTTPS par Traefik sous https://actus.broceliande.lan. À la fin, le site survit à la recréation des conteneurs et au redémarrage de la VM, la base est sauvegardée, et ni le port de l'application ni celui de la base ne sont joignables depuis le réseau. Note l'adresse de ta VM Debian : on l'appelle IP-VM ci-dessous.
Machine
Système
Rôle dans le TP
SRV-LINUX
Debian 13 (B2-14), Traefik installé en B2-12
Hôte des conteneurs : Docker Engine, Compose, projet /opt/actus, adresse IP-VM
PC-COMPTA
Windows 11 Pro
Poste client : navigateur, tests de ports
SRV-01
Windows Server 2025, DNS
Facultatif : enregistrement actus.broceliande.lan (sinon, fichier hosts du poste)
Seul Traefik est exposé ; l'application n'écoute que sur la boucle locale et la base reste dans son réseau interne.
Ce que tu ajoutes à cette étape
Le moteur de conteneurs : Docker Engine et Compose depuis le dépôt officiel, journaux limités en taille.
Le projet actus : compose.yaml durci et .env protégé, dans /opt/actus.
La publication : un fichier dynamique Traefik et un certificat pour actus.broceliande.lan.
L'exploitation : persistance vérifiée, sauvegarde de la base, procédure de mise à jour.
1 Installer Docker Engine
Sur SRV-LINUX, ajoute le dépôt officiel et installe les paquets comme en partie 3.1, puis limite la taille des journaux des conteneurs (partie 8.4) :
Debian 13 SRV-LINUX
$ sudo docker run --rm hello-world # Hello from Docker! $ sudo docker compose version # Docker Compose version v2.x $ echo '{ "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } }' | sudo tee /etc/docker/daemon.json $ sudo systemctl restart docker && sudo docker info | grep -i 'logging driver'
N'ajoute pas ton compte au groupe docker : toutes les commandes du TP se lancent avec sudo.
Les mots de passe sont tirés au hasard et ne s'affichent nulle part. Si tu changes un jour DB_PASSWORD, la base déjà initialisée garde l'ancien : il faudra aussi le changer dans MariaDB.
3 Écrire et valider compose.yaml
Crée /opt/actus/compose.yaml avec sudo nano en reprenant le fichier de la partie 6, puis ajoute à chacun des deux services ces deux lignes de durcissement (même niveau d'indentation que image), avec 1g de mémoire pour db et 512m pour web :
$ sudo docker compose config --quiet && echo "fichier valide" # sinon : numéro de ligne de l'erreur YAML $ sudo docker compose config --services # db et web
4 Démarrer et observer
Complète les commandes, à lancer dans /opt/actus avec sudo. Vérifie chaque ligne avec ✓.
démarrer en arrière-plan $
état des services $
journal du service web $
résolution de db depuis web $
tester sans Traefik $
Attends que db passe à healthy (quelques secondes) : web ne démarre qu'à ce moment. Le test local doit répondre 302 Found vers /wp-admin/install.php. N'ouvre pas l'installation par cette adresse : WordPress l'enregistrerait comme adresse du site (ticket n° 6). Repère aussi les objets créés : sudo docker network ls et sudo docker volume ls montrent les préfixes actus_.
Si Traefik n'est pas installé sur ta VM (B2-12 non fait), ajoute-le au projet comme dans la variante 7.1, avec url: "http://web:80" dans son fichier dynamique ; tu peux alors retirer la ligne ports de web.
6 Résoudre le nom et installer le site
Le nom : sur SRV-01, ajoute l'enregistrement Add-DnsServerResourceRecordA -ZoneName broceliande.lan -Name actus -IPv4Address IP-VM. Sans DNS du domaine, ajoute sur PC-COMPTA la ligne IP-VM actus.broceliande.lan au fichier C:\Windows\System32\drivers\etc\hosts (Bloc-notes en administrateur).
L'installation : depuis PC-COMPTA, ouvre https://actus.broceliande.lan, accepte une fois l'alerte du certificat autosigné, puis installe WordPress en français : titre « Actualités Brocéliande », un compte administrateur nominatif et un mot de passe robuste.
Un premier contenu : publie un article « Planning de l'entrepôt ». Il servira à prouver la persistance.
7 Exploiter : persistance, sauvegarde, mise à jour
Debian 13 /opt/actus
$ sudo docker compose down && sudo docker compose up -d # l'article est toujours là (volumes conservés) $ sudo docker compose exec -T db sh -c 'mariadb-dump -uroot -p"$MARIADB_ROOT_PASSWORD" wordpress' | gzip | sudo tee /root/actus-$(date +%F).sql.gz > /dev/null $ sudo zcat /root/actus-$(date +%F).sql.gz | grep -c 'CREATE TABLE' # une douzaine de tables WordPress (pas de * : ton shell ne peut pas lire /root) $ sudo docker compose pull && sudo docker compose up -d && sudo docker image prune -f # procédure de mise à jour $ sudo reboot # après redémarrage, le site revient seul (restart: unless-stopped)
Enfin, depuis PC-COMPTA, vérifie l'isolement dans PowerShell : Test-NetConnection IP-VM -Port 8081 et Test-NetConnection IP-VM -Port 3306 doivent répondre TcpTestSucceeded : False, alors que le port 443 répond True.
8 Valider : le cahier de recette
N°
Test
Commande
Résultat attendu
OK
1
Le moteur fonctionne
sudo docker run --rm hello-world
Hello from Docker!
2
Les secrets sont protégés
sudo ls -l /opt/actus/.env
-rw-------, propriétaire root
3
Les services sont sains
sudo docker compose ps
web Up, db Up (healthy)
4
Le site répond en HTTPS par Traefik
https://actus.broceliande.lan sur PC-COMPTA
Site WordPress, certificat actus.broceliande.lan
5
Application et base non exposées
Test-NetConnection IP-VM -Port 8081 puis 3306
Échec attendu : False pour les deux
6
Les données survivent aux conteneurs
docker compose down puis up -d
L'article « Planning de l'entrepôt » est présent
7
Le site revient après redémarrage
sudo reboot de la VM
Site accessible sans intervention
8
La base est sauvegardée
sudo sh -c "zcat /root/actus-*.sql.gz | grep -c 'CREATE TABLE'"
Nombre de tables non nul
Suis ta progressionConnecte-toi pour marquer les ressources comme terminées et retrouver ton avancement.