Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

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
dockerfile
# 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-reqs parce que l'image composer:2 n'a pas les extensions intl et pdo_mysql, présentes seulement dans l'image finale ;
  • libicu-dev est nécessaire pour compiler l'extension intl ; l'installation et le nettoyage sont dans le même RUN ;
  • les manifestes composer.* sont copiés avant le code, pour le cache ;
  • seul public/ est servi par Apache ; src/ et vendor/ 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
yaml
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
text
planning.ad.sorbier.example {
	tls /certs/planning.crt /certs/planning.key
	reverse_proxy planning:80
}
powershell
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 :

text
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).

  1. Quelle plage d'adresses Docker peut-il utiliser ?
  2. Combien de réseaux peut-il créer ?
  3. Combien de conteneurs au plus sur un de ces réseaux, sachant que Docker garde la première adresse utilisable pour la passerelle ?
  4. Avec "size": 26, que deviennent les réponses 2 et 3 ?
Voir la réponseMasquer la réponse de l'exercice 5
  1. Un /14 laisse 32 − 14 = 18 bits d'hôte, soit 2¹⁸ = 262 144 adresses : de 172.20.0.0 à 172.23.255.255.
  2. Chaque réseau est un /24 : 2²⁴⁻¹⁴ = 2¹⁰ = 1 024 réseaux, de 172.20.0.0/24 à 172.23.255.0/24.
  3. Un /24 compte 256 adresses, dont 254 utilisables (sans l'adresse de réseau ni celle de diffusion) ; la passerelle en prend une : 253 conteneurs.
  4. 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.

yaml
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 :

  1. Le service gestionnaire a 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 / ni privileged.
  2. 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.
  3. 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é.
  4. Image mariadb sans étiquette et sans volume : une mise à jour majeure imprévue, et des données perdues à la recréation. Correction : mariadb:11.8 et 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.

  1. Pourquoi deux conteneurs d'un même hôte ne peuvent-ils pas utiliser des noyaux Linux de versions différentes ?
  2. Que fait docker compose down, et que ne fait-il pas ?
  3. Pourquoi écrit-on CMD ["node", "src/server.js"] plutôt que CMD node src/server.js ?
  4. Quelle est la différence entre EXPOSE 3000 dans un Dockerfile et ports: ["3000:3000"] dans Compose ?
  5. Pourquoi l'accès au socket Docker équivaut-il à un accès root ?
Voir la réponseMasquer la réponse de l'exercice 7
  1. 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.
  2. Il arrête et supprime les conteneurs et les réseaux du projet ; il ne supprime pas les volumes (il faut -v pour cela), donc les données sont conservées.
  3. La forme exec lance node directement, comme processus principal : il reçoit le signal d'arrêt de docker stop et peut s'arrêter proprement. La forme shell passe par /bin/sh -c, et le programme ne reçoit pas le signal.
  4. EXPOSE documente le port écouté, sans rien ouvrir ; ports: publie réellement le port du conteneur sur un port de l'hôte.
  5. Le démon tourne en root et 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
  1. Prévenir les utilisateurs de l'interruption et ouvrir un ticket.
  2. 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
  3. Arrêter l'application, pas la base : docker compose stop planning.
  4. Vérifier que le fichier de sauvegarde est complet (taille non nulle, dernière ligne -- Dump completed).
  5. 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.
  6. Redémarrer l'application : docker compose start planning, puis vérifier docker compose ps et l'affichage des données dans l'application.
  7. 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é ?

Chapitre terminé !

Fais les 8 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : Aide-mémoire : les commandes Docker et ComposeRevoir le sommaire du cours