Chapitre 1515 minIntermédiaire
Exercices corrigés Docker type BTS SIO
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- conteneuriser une application PHP de bout en bout ;
- diagnostiquer une panne à partir des journaux ;
- analyser la sécurité d'un fichier Compose comme à l'E7 ;
- rédiger une procédure de restauration.
8 exercices corrigés
Ces exercices reprennent tout le cours au format des épreuves : une mise en situation chez Sorbier Logistique, des documents à analyser (fichiers, journaux, commandes) et des questions qui demandent de produire une configuration ou de justifier un choix. Fais-les sans regarder les chapitres, puis compare avec le corrigé.
Le contexte
Sorbier Logistique veut déployer sorbier-planning, une application PHP 8.5 de planification des tournées, avec une base MariaDB. L'équipe de développement fournit le code ; le service informatique (Antoine Mercier et les techniciens Chloé Lambert et Moussa Diallo) l'héberge sur srv-gestion (192.168.10.179, VLAN serveurs 192.168.10.176/28), où tournent déjà Docker Engine 29 et le reverse proxy Caddy du chapitre 9. Les salariés y accéderont par https://planning.ad.sorbier.example.
Exercice 1 sur 8 1. Le Dockerfile de l'application Réussi À revoir
Le code de sorbier-planning est organisé ainsi : public/ (la racine web, avec index.php), src/ (les classes), composer.json et composer.lock. L'application a besoin des extensions PHP pdo_mysql et intl. Écris un Dockerfile qui part de php:8.5-apache, installe les extensions, installe les dépendances Composer sans les outils de développement grâce à une étape de construction séparée basée sur l'image officielle composer:2, et n'embarque pas Composer dans l'image finale.
Voir la réponseMasquer la réponse de l'exercice 1
# syntax=docker/dockerfile:1
# Étape 1 : dépendances PHP
FROM composer:2 AS dependances
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader --ignore-platform-reqs
# Étape 2 : l'image finale
FROM php:8.5-apache
RUN apt-get update \
&& apt-get install -y --no-install-recommends libicu-dev \
&& docker-php-ext-install pdo_mysql intl \
&& rm -rf /var/lib/apt/lists/* \
&& mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
COPY --from=dependances --chown=www-data:www-data /app/vendor /var/www/vendor
COPY --chown=www-data:www-data src/ /var/www/src/
COPY --chown=www-data:www-data public/ /var/www/html/
Points notés :
- multi-stage : Composer n'est présent que dans la première étape ; l'image finale ne contient que
vendor/; --no-dev: pas d'outils de test en production ;--ignore-platform-reqsparce que l'imagecomposer:2n'a pas les extensionsintletpdo_mysql, présentes seulement dans l'image finale ;libicu-devest nécessaire pour compiler l'extensionintl; l'installation et le nettoyage sont dans le mêmeRUN;- les manifestes
composer.*sont copiés avant le code, pour le cache ; - seul
public/est servi par Apache ;src/etvendor/sont hors de la racine web (le code charge../vendor/autoload.php).
Tu avais trouvé ?
Exercice 2 sur 8 2. Le fichier compose.yaml Réussi À revoir
Écris les services à ajouter au compose.yaml de srv-gestion (projet gestion) pour sorbier-planning (image ghcr.io/sorbier/sorbier-planning:1.0.0) et sa base mariadb:11.8, avec : un réseau interne propre à la planification, partagé par l'application et sa base seulement ; l'application également sur le réseau frontal du proxy ; les mots de passe en secrets (fichiers secrets/planning_db.txt et secrets/planning_root.txt) ; un volume nommé pour la base ; un contrôle de santé de la base, attendu par l'application. L'image MariaDB fournit le script healthcheck.sh, appelé avec --connect --innodb_initialized.
Voir la réponseMasquer la réponse de l'exercice 2
services:
planning:
image: ghcr.io/sorbier/sorbier-planning:1.0.0
environment:
DB_HOST: planning-bdd
DB_NAME: planning
DB_USER: planning
DB_PASSWORD_FILE: /run/secrets/planning_db
secrets:
- planning_db
networks:
- frontal
- planning-donnees
depends_on:
planning-bdd:
condition: service_healthy
restart: unless-stopped
planning-bdd:
image: mariadb:11.8
environment:
MARIADB_DATABASE: planning
MARIADB_USER: planning
MARIADB_PASSWORD_FILE: /run/secrets/planning_db
MARIADB_ROOT_PASSWORD_FILE: /run/secrets/planning_root
secrets:
- planning_db
- planning_root
volumes:
- planning-mariadb:/var/lib/mysql
networks:
- planning-donnees
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
restart: unless-stopped
networks:
planning-donnees:
internal: true
volumes:
planning-mariadb:
secrets:
planning_db:
file: ./secrets/planning_db.txt
planning_root:
file: ./secrets/planning_root.txt
Ces blocs se fusionnent avec ceux qui existent déjà : frontal est déjà déclaré sous networks:. Aucun port n'est publié. Le fichier planning_db.txt doit être lisible par l'utilisateur de l'application (www-data, UID 33, dans l'image php:8.5-apache) : chown root:33 et chmod 640. L'application PHP doit lire DB_PASSWORD_FILE (trim(file_get_contents(getenv('DB_PASSWORD_FILE')))).
Tu avais trouvé ?
Exercice 3 sur 8 3. Publier en HTTPS Réussi À revoir
Écris le bloc à ajouter au Caddyfile, sachant que le certificat signé par l'autorité interne est dans certs/planning.crt et certs/planning.key, et que l'application écoute sur le port 80 de son conteneur. Donne la commande PowerShell qui crée l'enregistrement DNS sur le contrôleur de domaine, et la règle à ajouter sur le pare-feu de l'entreprise pour les postes (192.168.10.64/26).
Voir la réponseMasquer la réponse de l'exercice 3
planning.ad.sorbier.example {
tls /certs/planning.crt /certs/planning.key
reverse_proxy planning:80
}
Add-DnsServerResourceRecordA -ZoneName "ad.sorbier.example" -Name "planning" -IPv4Address "192.168.10.179"
Règle de pare-feu (interface POSTES) : source 192.168.10.64/26, destination 192.168.10.179, port TCP 443, autoriser. Elle existe déjà si sorbier-frais est publié sur la même adresse : les deux applications partagent l'adresse et le port 443, Caddy les distingue par le nom. Le port 80 peut être autorisé aussi, pour la redirection vers HTTPS.
Tu avais trouvé ?
Exercice 4 sur 8 4. Diagnostic sur journaux Réussi À revoir
Au premier lancement, docker compose ps montre gestion-planning-1 en Restarting, et docker compose logs planning affiche :
PHP Fatal error: Uncaught PDOException: SQLSTATE[HY000] [2002] No such file or directory in /var/www/src/Base.php:14
Le développeur a laissé dans son code new PDO('mysql:host=localhost;dbname=planning', …). Explique l'erreur et sa correction. Puis, une fois corrigée, la page affiche « Access denied for user 'planning' » ; la veille, Moussa avait fait un premier essai avec un autre mot de passe. Explique.
Voir la réponseMasquer la réponse de l'exercice 4
Première erreur. Avec host=localhost, PDO ne passe pas par le réseau : il cherche le socket Unix local de MySQL, qui n'existe pas dans le conteneur de l'application (d'où « No such file or directory »). Avec 127.0.0.1, ce serait « Connection refused » : dans le conteneur, cette adresse est le conteneur lui-même, où aucun MariaDB n'écoute. Il faut utiliser le nom du service de la base sur le réseau partagé, ici planning-bdd, idéalement lu depuis la variable DB_HOST plutôt qu'écrit en dur : "mysql:host=" . getenv('DB_HOST') . ";dbname=planning".
Seconde erreur. L'image MariaDB n'initialise la base (et le compte planning avec son mot de passe) que si le volume est vide. Le volume gestion_planning-mariadb a été créé lors de l'essai de la veille, avec l'ancien mot de passe : le nouveau secret est ignoré. Comme la base ne contient encore rien d'utile, on peut supprimer le volume et relancer (docker compose down, docker volume rm gestion_planning-mariadb, docker compose up -d). Si elle contenait des données, on changerait le mot de passe dans MariaDB (ALTER USER 'planning'@'%' IDENTIFIED BY '…';).
Tu avais trouvé ?
Exercice 5 sur 8 5. Les sous-réseaux de Docker Réussi À revoir
Le daemon.json de srv-gestion contient "default-address-pools": [{ "base": "172.20.0.0/14", "size": 24 }] (le pont par défaut est fixé hors de cette plage par bip).
- Quelle plage d'adresses Docker peut-il utiliser ?
- Combien de réseaux peut-il créer ?
- Combien de conteneurs au plus sur un de ces réseaux, sachant que Docker garde la première adresse utilisable pour la passerelle ?
- Avec
"size": 26, que deviennent les réponses 2 et 3 ?
Voir la réponseMasquer la réponse de l'exercice 5
- Un
/14laisse 32 − 14 = 18 bits d'hôte, soit 2¹⁸ = 262 144 adresses : de172.20.0.0à172.23.255.255. - Chaque réseau est un
/24: 2²⁴⁻¹⁴ = 2¹⁰ = 1 024 réseaux, de172.20.0.0/24à172.23.255.0/24. - Un
/24compte 256 adresses, dont 254 utilisables (sans l'adresse de réseau ni celle de diffusion) ; la passerelle en prend une : 253 conteneurs. - Avec des
/26: 2²⁶⁻¹⁴ = 2¹² = 4 096 réseaux, de 64 adresses chacun, soit 62 utilisables et 61 conteneurs une fois la passerelle retirée.
Tu avais trouvé ?
Exercice 6 sur 8 6. Analyse de sécurité type E7 Réussi À revoir
Un prestataire a laissé ce fichier sur un serveur d'un site distant de Sorbier. Relève les problèmes de sécurité, classe-les du plus grave au moins grave, et propose une correction pour chacun.
services:
gestionnaire:
image: outilsdocker/gestion-conteneurs
ports:
- "9000:9000"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /:/hote
privileged: true
bdd:
image: mariadb
ports:
- "3306:3306"
environment:
MARIADB_ROOT_PASSWORD: admin
Voir la réponseMasquer la réponse de l'exercice 6
Du plus grave au moins grave :
- Le service
gestionnairea les pleins pouvoirs sur l'hôte : socket Docker monté, racine de l'hôte montée en écriture, mode privilégié, et interface publiée sur le port 9000 de toutes les adresses, en HTTP. Quiconque atteint ce port (ou exploite une faille de l'outil) contrôle le serveur. Correction : supprimer ce service ; s'il est vraiment nécessaire, le limiter au réseau d'administration, derrière le reverse proxy en HTTPS avec authentification forte, sans/niprivileged. - L'image vient d'un compte inconnu (
outilsdocker/…), sans étiquette : contenu non vérifié, peut-être malveillant, et version qui change sans prévenir. Correction : image officielle ou d'un éditeur vérifié, à une version précise, analysée par un scanner. - La base est publiée sur le réseau avec le mot de passe
root« admin » : un mot de passe trivial, en clair dans le fichier. Correction : aucun port publié, réseau interne avec la seule application, mot de passe long dans un secret, compte applicatif dédié. - Image
mariadbsans étiquette et sans volume : une mise à jour majeure imprévue, et des données perdues à la recréation. Correction :mariadb:11.8et un volume nommé sur/var/lib/mysql.
Critères atteints : confidentialité, intégrité et disponibilité de tout le serveur, et des données de la base. À l'écrit, présente ces éléments dans un tableau « constat, risque, mesure » (méthode de l'E7 SISR).
Tu avais trouvé ?
Exercice 7 sur 8 7. Questions de cours Réussi À revoir
Réponds en une ou deux phrases.
- Pourquoi deux conteneurs d'un même hôte ne peuvent-ils pas utiliser des noyaux Linux de versions différentes ?
- Que fait
docker compose down, et que ne fait-il pas ? - Pourquoi écrit-on
CMD ["node", "src/server.js"]plutôt queCMD node src/server.js? - Quelle est la différence entre
EXPOSE 3000dans un Dockerfile etports: ["3000:3000"]dans Compose ? - Pourquoi l'accès au socket Docker équivaut-il à un accès
root?
Voir la réponseMasquer la réponse de l'exercice 7
- Un conteneur n'a pas de noyau : tous les conteneurs d'un hôte partagent le noyau de l'hôte. Pour un autre noyau, il faut une machine virtuelle.
- Il arrête et supprime les conteneurs et les réseaux du projet ; il ne supprime pas les volumes (il faut
-vpour cela), donc les données sont conservées. - La forme exec lance
nodedirectement, comme processus principal : il reçoit le signal d'arrêt dedocker stopet peut s'arrêter proprement. La forme shell passe par/bin/sh -c, et le programme ne reçoit pas le signal. EXPOSEdocumente le port écouté, sans rien ouvrir ;ports:publie réellement le port du conteneur sur un port de l'hôte.- Le démon tourne en
rootet exécute toute requête reçue sur son socket, y compris « lancer un conteneur privilégié qui monte la racine de l'hôte » : qui peut écrire sur le socket peut donc modifier n'importe quel fichier de l'hôte.
Tu avais trouvé ?
Exercice 8 sur 8 8. Rédiger la procédure de restauration Réussi À revoir
Rédige, en étapes numérotées, la procédure qui permet à un technicien de restaurer la base planning à partir de l'export de la veille /srv/sauvegardes/planning-2026-11-02.sql, après une suppression accidentelle de données. L'application doit être arrêtée pendant la restauration, la base restant en marche.
Voir la réponseMasquer la réponse de l'exercice 8
- Prévenir les utilisateurs de l'interruption et ouvrir un ticket.
- Faire un export de l'état actuel, même abîmé, pour pouvoir revenir en arrière :
docker compose exec -T planning-bdd sh -c 'exec mariadb-dump --single-transaction --databases planning -uroot -p"$(cat /run/secrets/planning_root)"' > /srv/sauvegardes/planning-avant-restauration.sql - Arrêter l'application, pas la base :
docker compose stop planning. - Vérifier que le fichier de sauvegarde est complet (taille non nulle, dernière ligne
-- Dump completed). - Restaurer :
docker compose exec -T planning-bdd sh -c 'exec mariadb -uroot -p"$(cat /run/secrets/planning_root)"' < /srv/sauvegardes/planning-2026-11-02.sql. - Redémarrer l'application :
docker compose start planning, puis vérifierdocker compose pset l'affichage des données dans l'application. - Informer les utilisateurs que les saisies faites depuis la sauvegarde de la veille sont à refaire, et clore le ticket en notant la cause de la suppression.
Dans les images MariaDB récentes, les clients s'appellent mariadb et mariadb-dump (les anciens noms mysql et mysqldump ne sont plus garantis).
Tu avais trouvé ?