Aller au contenu
Réussir mon BTS SIO

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

Par exemple

Chapitre 1214 minIntermédiaire

Docker dans la CI/CD : construire, publier et déployer une image

Par Josselyn C., prof de BTS SIO. Mis à jour le .

À la fin de ce chapitre, tu sauras

  • décrire le circuit d'une image, du commit à la production ;
  • construire pour la bonne architecture et publier dans un registre ;
  • installer un registre privé protégé par HTTPS et mot de passe ;
  • écrire un script de déploiement qui revient en arrière en cas d'échec.

4 exercices corrigés

Utiliser Docker dans la CI/CD, c'est confier au pipeline la construction de l'image à chaque modification du code, son test, son analyse de sécurité et sa publication dans un registre, puis déployer sur le serveur exactement l'image qui a été testée. Ce chapitre présente le circuit complet de sorbier-frais, de GitHub au serveur, installe un registre privé pour les outils internes, et écrit le script de déploiement de srv-gestion. Le détail des workflows est dans le cours Intégration continue.

Le circuit d'une image, du commit au serveur

ÉtapeOùCe qui se passe
1. Commit et pull requestGitHubLe code est relu ; la CI construit l'image et lance les tests, sans rien publier
2. Fusion sur main ou tag v2.5.0GitHub ActionsLa CI construit l'image, lance les tests, l'analyse avec un scanner, puis la pousse dans ghcr.io avec ses étiquettes
3. RecetteServeur de recetteOn y déploie l'image sha-… ou 2.5.0 et on vérifie avec les utilisateurs
4. Productionsrv-gestionOn déploie la même image, sans la reconstruire

Le workflow GitHub Actions, en bref

Le workflow de sorbier-frais utilise les actions officielles de Docker :

yaml
      - uses: docker/setup-buildx-action@v4
      - uses: docker/login-action@v4
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - id: meta
        uses: docker/metadata-action@v6
        with:
          images: ghcr.io/sorbier/sorbier-frais
          tags: |
            type=sha
            type=semver,pattern={{version}}
      - uses: docker/build-push-action@v7
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

Le jeton GITHUB_TOKEN du workflow suffit pour pousser dans ghcr.io, à condition d'accorder la permission packages: write au job. Le workflow complet (déclencheurs, cache, permissions, test de fumée, équivalent GitLab CI) est expliqué pas à pas dans le chapitre Construire et publier une image Docker dans la CI.

Entre la construction et la publication, on ajoute l'analyse de l'image. Pour ne pas dépendre d'une action tierce, on peut lancer le scanner lui-même, en conteneur, à une version précise :

yaml
      - name: Analyser l'image
        run: |
          docker run --rm \
            -v /var/run/docker.sock:/var/run/docker.sock \
            aquasec/trivy:${TRIVY_VERSION}@${TRIVY_DIGEST} image \
            --severity CRITICAL --ignore-unfixed --exit-code 1 \
            sorbier-frais:test

Ici, monter le socket est acceptable : le runner de la CI est une machine jetable, détruite à la fin du job, et Trivy a besoin de lire l'image construite localement. La version et l'empreinte (sha256:…) de l'image Trivy sont fixées dans le workflow (variables TRIVY_VERSION et TRIVY_DIGEST), à une version vérifiée, jamais latest : une étiquette peut être réécrite, comme en mars 2026, une empreinte non. L'image testée est d'abord construite localement (docker build -t sorbier-frais:test .), puis poussée seulement si l'analyse passe (détail).

Construire pour la bonne architecture

Une image construite sur un Mac à processeur Apple Silicon est en arm64 ; srv-gestion est en amd64. Lancée sur le serveur, elle échoue avec exec format error. Trois solutions :

  • construire dans la CI, dont les runners sont en amd64 ;
  • préciser la plateforme : docker build --platform linux/amd64 -t … . ;
  • construire une image multi-architecture avec Buildx : docker buildx build --platform linux/amd64,linux/arm64 -t … --push ..

Un registre privé pour les outils internes

Les applications de Sorbier sont dans ghcr.io, privées. Pour les petits outils internes (scripts de sauvegarde, images de dépannage) et pour garder une copie des images critiques dans l'entreprise, le service informatique installe un registre privé sur srv-gestion, derrière le même proxy Caddy, à l'adresse registre.ad.sorbier.example.

Le registre libre de Docker (registry:3) ne fournit aucune interface et peu de sécurité : on lui ajoute une authentification par fichier htpasswd (mots de passe hachés avec bcrypt) et on le place derrière le proxy HTTPS. Le fichier de mots de passe se génère avec l'outil htpasswd de l'image Apache, l'image du registre ne le contenant plus :

bash
sudo mkdir -p /srv/gestion/registre
docker run --rm --entrypoint htpasswd httpd:2.4 -Bbn ci-sorbier 'Un-Mot-De-Passe-Long-2026' \
  | sudo tee /srv/gestion/registre/htpasswd > /dev/null

Le service, ajouté au compose.yaml de srv-gestion :

yaml
  registre:
    image: registry:3
    environment:
      REGISTRY_AUTH: htpasswd
      REGISTRY_AUTH_HTPASSWD_REALM: "Registre Sorbier"
      REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
    volumes:
      - ./registre/htpasswd:/auth/htpasswd:ro
      - registre-donnees:/var/lib/registry
    networks:
      - frontal
    restart: unless-stopped

(avec registre-donnees: sous la clé volumes: du bas), et dans le Caddyfile :

text
registre.ad.sorbier.example {
	tls /certs/registre.crt /certs/registre.key
	reverse_proxy registre:5000
}

puis lance le registre et recharge Caddy, qui ne relit pas seul son fichier :

bash
sudo docker compose up -d
sudo docker compose exec -w /etc/caddy proxy caddy reload

Depuis le poste d'administration :

bash
docker login registre.ad.sorbier.example
docker tag outil-sauvegarde:1.2 registre.ad.sorbier.example/outils/sauvegarde:1.2
docker push registre.ad.sorbier.example/outils/sauvegarde:1.2

Le script de déploiement de srv-gestion

Le déploiement en production se fait par un script, versionné avec compose.yaml, qui fait toujours les mêmes gestes dans le même ordre : exporter la base, changer la version, récupérer les images, relancer, vérifier, et revenir en arrière si la vérification échoue.

bash
#!/usr/bin/env bash
# deployer.sh : déploie une version de sorbier-frais sur srv-gestion
# Usage : sudo ./deployer.sh 2.5.0
set -euo pipefail

VERSION="${1:?Usage : $0 <version>}"
DOSSIER=/srv/gestion
URL_SANTE=https://gestion.ad.sorbier.example/api/health
cd "$DOSSIER"

ANCIENNE=$(grep '^VERSION=' .env | cut -d= -f2)
echo "Version actuelle : $ANCIENNE, version demandée : $VERSION"

# 1. Export de la base avant toute modification
mkdir -p /srv/sauvegardes
docker compose exec -T bdd sh -c 'exec mysqldump --single-transaction --databases frais -uroot -p"$(cat /run/secrets/db_root_password)"' \
  > "/srv/sauvegardes/frais-avant-$VERSION-$(date +%F-%H%M).sql"

# 2. Nouvelle version et images
sed -i "s/^VERSION=.*/VERSION=$VERSION/" .env
docker compose pull api front
docker compose up -d

# 3. Vérification : l'API doit répondre dans les 60 secondes
for essai in $(seq 1 12); do
  if curl --fail --silent --output /dev/null "$URL_SANTE"; then
    echo "Version $VERSION en service."
    exit 0
  fi
  sleep 5
done

# 4. Échec : retour à la version précédente
echo "Échec de la vérification, retour à la version $ANCIENNE." >&2
sed -i "s/^VERSION=.*/VERSION=$ANCIENNE/" .env
docker compose up -d
exit 1
PartieRôle
set -euo pipefailLe script s'arrête à la première erreur, sur une variable non définie, ou si une commande d'un tube échoue
${1:?…}Refuse de démarrer sans numéro de version
exec -T bdd … mysqldumpExport logique de la base dans le conteneur ; -T désactive le terminal, indispensable pour rediriger la sortie vers un fichier
$(cat /run/secrets/db_root_password)Entre apostrophes simples, la substitution est faite dans le conteneur, qui lit son secret : le mot de passe n'apparaît pas sur l'hôte
La boucle forDouze essais espacés de 5 secondes ; curl --fail renvoie une erreur sur un code HTTP 4xx ou 5xx
Le retour arrièreRemet l'ancienne version ; la base, elle, n'est pas restaurée automatiquement : c'est une décision humaine, avec l'export à disposition

Le script ne restaure pas la base de lui-même : si la nouvelle version a déjà modifié la structure des tables, l'administrateur décide de restaurer l'export après avoir analysé la situation. L'écriture de scripts Bash robustes est détaillée dans le cours Scripts PowerShell et Bash. À plus grande échelle, ces gestes sont automatisés par un outil comme Ansible, ou par la CI elle-même (déploiement continu).

Exercices

Exercice 1 sur 4 Promouvoir la même image Réussi À revoir

La recette de sorbier-frais 2.5.0 est validée. Un développeur propose de relancer docker build sur la branche main pour produire l'image de production « pour être sûr d'avoir la dernière version ». Explique pourquoi c'est une mauvaise idée et ce qu'il faut faire.

Voir la réponseMasquer la réponse de l'exercice 1

Une nouvelle construction produit une autre image : l'image de base node:24-slim a pu changer entre-temps, une dépendance aussi, et main a peut-être reçu d'autres commits. On déploierait en production une image qui n'a jamais été testée en recette.

Il faut déployer la même image que celle de la recette : son étiquette fixe (2.5.0) ou, mieux, son empreinte @sha256:…. C'est le principe « construire une fois, déployer partout ».

Tu avais trouvé ?

Exercice 2 sur 4 exec format error Réussi À revoir

Un développeur construit l'image sur son Mac M3, la pousse dans le registre, et sur srv-gestion le conteneur s'arrête aussitôt avec exec /usr/local/bin/docker-entrypoint.sh: exec format error. Explique et donne deux solutions.

Voir la réponseMasquer la réponse de l'exercice 2

L'image a été construite pour l'architecture du Mac, arm64 ; srv-gestion est en amd64 (x86-64). Le processeur du serveur ne sait pas exécuter les binaires de l'image.

Solutions : construire l'image dans la CI (runners amd64), ce qui est de toute façon la bonne pratique ; ou construire en précisant la plateforme, docker build --platform linux/amd64 … ; ou publier une image multi-architecture avec docker buildx build --platform linux/amd64,linux/arm64 … --push.

Tu avais trouvé ?

Exercice 3 sur 4 Relire le script de déploiement Réussi À revoir

Que se passe-t-il, avec le script deployer.sh de ce chapitre, dans chacun de ces cas ?

  1. L'administrateur tape sudo ./deployer.sh sans argument.
  2. La nouvelle image n'existe pas dans le registre (faute de frappe dans la version).
  3. La nouvelle version démarre, mais /health renvoie une erreur 500 pendant deux minutes.
Voir la réponseMasquer la réponse de l'exercice 3
  1. ${1:?…} arrête le script tout de suite avec le message d'usage : rien n'est modifié.
  2. L'export de la base est fait, .env est modifié, puis docker compose pull échoue ; avec set -e, le script s'arrête sans relancer les conteneurs. Les anciens conteneurs tournent toujours, mais .env contient la mauvaise version : le prochain docker compose up -d échouerait. Il faut corriger .env à la main. (Amélioration possible : faire le pull avec la nouvelle version avant de modifier .env, par exemple VERSION=$VERSION docker compose pull api front, puisqu'une variable d'environnement du shell est prioritaire sur le fichier .env.)
  3. Les douze essais échouent (60 secondes), le script remet l'ancienne version dans .env, relance docker compose up -d et se termine avec le code 1. Le service est rétabli sur l'ancienne version ; l'export de la base est disponible si la nouvelle version a modifié des données.

Tu avais trouvé ?

Exercice 4 sur 4 Le registre sans HTTPS Réussi À revoir

Un tutoriel propose de lancer registry:3 sur le port 5000 en HTTP et d'ajouter "insecure-registries": ["192.168.10.179:5000"] dans le daemon.json de chaque serveur. Donne deux risques et l'alternative.

Voir la réponseMasquer la réponse de l'exercice 4

Risques :

  • le mot de passe de docker login circule en clair sur le réseau : quiconque capture le trafic peut le réutiliser et pousser ses propres images ;
  • les images peuvent être modifiées en route (interception) : un serveur pourrait exécuter une image piégée en la croyant authentique ;
  • sans authentification du tout, n'importe quel poste du réseau peut lire ou remplacer les images.

Alternative : le registre derrière le reverse proxy en HTTPS, avec un certificat de l'autorité interne (que les serveurs reconnaissent déjà) et une authentification (htpasswd, ou Harbor avec des comptes nominatifs et des comptes robots pour la CI).

Tu avais trouvé ?

Chapitre terminé !

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

chapitres
–
de série
–
du cours
–
Chapitre suivant : Docker sur Proxmox VE : dans une VM ou dans un conteneur LXC ?Revoir le sommaire du cours