Leçon 8/9 10 min

Robustesse et rollback

Un deploy Docker pousse une image en double tag (main + sha). Rollback : un tag dans le .env et relancer compose up.

14h23 : la prod vient de tomber

La pipeline GitHub Actions vient de passer au vert. Tu souffles. Une minute plus tard, ton téléphone vibre : le site est en 500. Pourtant, l'image Docker a bien été construite et poussée sur GHCR. Le souci, c'est ce qui se passe sur le VPS après : le docker compose pull démarre, mais la migration de base de données plantée à mi-chemin laisse le schéma incohérent. L'app tourne, mais sur une base à moitié migrée. Ce n'est pas un bug de l'app : c'est une faiblesse du processus.

Ce que tu viens de vivre illustre ce qui peut encore casser avec Docker, même si une image est tout-ou-rien. Une image est un artefact complet ou absent : pas d'état à moitié construit, pas de modules manquants. Et docker compose up -d --wait ne déclare la bascule réussie que si le conteneur passe à l'état healthy. Mais deux pièges restent : un bug logique qui passe les tests, et une migration qui se passe mal. D'où la règle : savoir revenir en arrière en quelques secondes.

Docker règle le problème du déploiement partiel (l'image est tout-ou-rien), mais pas celui du bug logique ni celui de la migration ratée. Si ton healthcheck renvoie 500 après le up --wait, tu dois pouvoir basculer sur la version d'avant sans délai ni compilation.

Le double tag : main et sha

La CI (leçon 6) construit l'image et la pousse sur GHCR avec deux tags à chaque commit :

  • :main : la dernière version connue comme bonne, que le VPS suit en temps normal.
  • :sha-e4f5g6h : la photo immuable de ce commit précis. Elle ne bouge jamais.

Sur le VPS, le compose.yml déclare l'image avec une variable :

services:
  php:
    image: ghcr.io/toi/ton-projet:${TAG:-main}

Et le .env à côté contient simplement :

TAG=main

En temps normal, le VPS tire toujours l'image :main, la plus récente. La photo :sha- est là pour le rollback.

Le tag :sha- reste sur le disque du VPS après le premier pull. Si tu dois revenir dessus, la bascule ne nécessite souvent aucun téléchargement : Docker réutilise les layers déjà en cache.

Le rollback : changer un tag, 15 secondes

Le healthcheck renvoie 500 après le deploy de i7j8k9l. Tu n'as pas à recompiler, pas à cloner le dépôt, pas à relancer la CI. Tu échanges une photo contre une autre :

# 1. Quelle était la version saine d'hier ?
#    (l'historique est sur GHCR et dans git log)
git log --oneline -3
# e4f5g6h hier 18h30 - feat: page contact
# i7j8k9l il y a 2 min - feat: refacto email (CASSÉ)

# 2. Sur le VPS : pointer le tag sain dans le .env
TAG=sha-e4f5g6h

# 3. Appliquer et attendre le feu vert du healthcheck
docker compose up -d --wait

Ce que fait up -d --wait : il recrée uniquement les conteneurs dont l'image a changé, attend que chacun passe healthy, et sort avec un code 0 si tout va bien ou 1 si le healthcheck échoue. La bascule prend 10 à 15 secondes. Aucune compilation. Aucun git. Tu échanges une photo contre une autre.

Teste le rollback une fois par mois, comme un exercice incendie. Le jour où tu en as besoin pour de vrai, tu n'as pas envie de le découvrir sous pression.

Les migrations : re-pointer l'image ne dé-migre pas la base. Si le commit cassé a ajouté une colonne en base, cette colonne est toujours là après le rollback d'image. Règle d'or : écris des migrations rétrocompatibles. Ajouter une colonne, oui. La supprimer dans le même commit que le code qui l'utilisait encore, non. Le rollback d'image ne règle pas un schéma cassé : planifie ta migration en conséquence.

La méthode classique, sans Docker

Sans Docker, le même principe s'appelle le pattern Capistrano/Deployer : on construit la nouvelle version dans un dossier daté à côté (releases/20260603_1423/), et on bascule d'un coup en re-pointant un symlink current vers ce dossier. Rollback = re-pointer current vers le dossier précédent + systemctl restart mon-service. Même logique (préparer à côté, basculer d'un coup, garder l'ancien), implémentée avec des dossiers et un symlink au lieu d'images et de tags.

/opt/app/
├── releases/
│   ├── 20260601_0910/   ← avant-hier
│   ├── 20260602_1830/   ← hier soir (saine)
│   └── 20260603_1423/   ← cassée
└── current -> releases/20260602_1830

Le schéma : images taguées sur GHCR, tag du .env

Trois images taguées sur GHCR empilées : sha-e4f5g6h (saine, en vert), sha-i7j8k9l (cassée, en rouge), main (alias flottant). Une flèche annotée TAG=sha-e4f5g6h depuis le .env du VPS pointe vers l'image saine, illustrant le rollback. GHCR (registre) :sha-e4f5g6h hier 18h30 - saine :sha-i7j8k9l 14h23 - cassée :main alias flottant = dernier sha VPS .env TAG=sha-e4f5g6h docker compose up -d --wait Conteneur recréé healthy en ~15 secondes
Le rollback, c'est re-pointer le TAG dans le .env vers un sha immuable et relancer docker compose up -d --wait. Aucune compilation. Les volumes (base de données) ne bougent pas.
Prédis avant de lire

Pendant que docker compose up -d --wait recrée le conteneur php sur l'image d'hier, que voit le visiteur, et que deviennent les données de la base ?

Voir la réponse

Coupure quasi nulle le temps de la recréation du conteneur (quelques secondes), puis le site revient. Les données de la base ne bougent pas : elles vivent dans le volume pgdata, indépendant des images. Changer de tag ne touche pas aux volumes.

À toi : rollback par tag en situation réelle

Le deploy de i7j8k9l casse la prod à 14h23. Le healthcheck renvoie 500. Tu as les images sur GHCR et en cache local. Mission : revenir sur sha-e4f5g6h sans rien compiler.

🖥️ Terminal simule · rollback prod Docker
$

14:23 — production just went down

The GitHub Actions pipeline just went green. You breathe. A minute later, your phone buzzes: the site is returning 500. The Docker image was built and pushed to GHCR just fine. The issue happened on the VPS: the docker compose pull started, but a database migration crashed halfway, leaving an inconsistent schema. The app is running — on a half-migrated database. This isn't a bug in the app: it's a weakness in the process.

What you just witnessed shows what can still go wrong with Docker, even though a Docker image is all-or-nothing. An image is a complete or absent artifact: no half-built state, no missing modules. And docker compose up -d --wait only declares the switch successful once the container reaches healthy. But two traps remain: a logic bug that slips through tests, and a migration that goes wrong. Hence the rule: know how to roll back in a matter of seconds.

Docker solves the partial-deploy problem (an image is all-or-nothing), but not logic bugs or failed migrations. If your healthcheck returns 500 after the up --wait, you must be able to switch back to the previous version without delay or compilation.

The double tag: main and sha

The CI (lesson 6) builds the image and pushes it to GHCR with two tags on every commit:

  • :main — the latest version known to be good, which the VPS follows in normal operation.
  • :sha-e4f5g6h — the immutable snapshot of that exact commit. It never changes.

On the VPS, compose.yml declares the image with a variable:

services:
  php:
    image: ghcr.io/you/your-project:${TAG:-main}

And the .env next to it contains simply:

TAG=main

Under normal operation the VPS always pulls the latest :main image. The :sha- snapshot is there for rollback.

The :sha- tag stays on the VPS disk after the first pull. If you need to roll back to it, the switch often requires no download at all: Docker reuses the layers already in cache.

The rollback: change a tag, 15 seconds

The healthcheck returns 500 after the i7j8k9l deploy. No recompilation, no repo clone, no CI re-run. You swap one snapshot for another:

# 1. What was the healthy version from yesterday?
#    (history is on GHCR and in git log)
git log --oneline -3
# e4f5g6h yesterday 18:30 - feat: contact page
# i7j8k9l 2 min ago - feat: email refacto (BROKEN)

# 2. On the VPS: point to the healthy tag in .env
TAG=sha-e4f5g6h

# 3. Apply and wait for the healthcheck green light
docker compose up -d --wait

What up -d --wait does: it recreates only the containers whose image changed, waits for each to become healthy, and exits with code 0 on success or 1 if the healthcheck fails. The switch takes 10 to 15 seconds. No compilation. No git. You swap one snapshot for another.

Test the rollback once a month, like a fire drill. The day you need it for real, you don't want to discover it under pressure.

Watch out for migrations: re-pointing the image does not un-migrate the database. If the broken commit added a column, that column is still there after the image rollback. Golden rule: write backward-compatible migrations. Adding a column, yes. Dropping it in the same commit as the code that was still using it, no. An image rollback can't fix a broken schema — plan your migrations accordingly.

The classic approach, without Docker

Without Docker, the same principle is called the Capistrano/Deployer pattern: you build the new version in a dated folder alongside the live one (releases/20260603_1423/), then switch atomically by re-pointing a current symlink. Rollback = re-point current at the previous folder + systemctl restart my-service. Same logic (prepare alongside, switch atomically, keep the old one), implemented with folders and a symlink instead of images and tags.

/opt/app/
├── releases/
│   ├── 20260601_0910/   ← day before yesterday
│   ├── 20260602_1830/   ← last night (healthy)
│   └── 20260603_1423/   ← broken
└── current -> releases/20260602_1830

The diagram: tagged images on GHCR, .env tag

Three tagged images stacked on GHCR: sha-e4f5g6h (healthy, green), sha-i7j8k9l (broken, red), main (floating alias). An arrow labelled TAG=sha-e4f5g6h from the VPS .env points to the healthy image, illustrating the rollback. GHCR (registry) :sha-e4f5g6h yesterday 18:30 - healthy :sha-i7j8k9l 14:23 - broken :main floating alias = latest sha VPS .env TAG=sha-e4f5g6h docker compose up -d --wait Container recreated healthy in ~15 seconds
The rollback is just re-pointing TAG in the .env to an immutable sha and re-running docker compose up -d --wait. No compilation. Volumes (database) are untouched.
Predict before reading on

While docker compose up -d --wait recreates the php container on yesterday's image, what does the visitor see, and what happens to the database data?

Show the answer

Near-zero downtime during the container recreation (a few seconds), then the site comes back. The database data doesn't move: it lives in the pgdata volume, independent of the images. Changing the tag doesn't touch volumes.

Your turn: tag-based rollback in a real situation

The i7j8k9l deploy broke production at 14:23. The healthcheck returns 500. You have the images on GHCR and in local cache. Mission: get back to sha-e4f5g6h without compiling anything.

🖥️ Simulated terminal · Docker prod rollback
$

🎯 Pratique

S'entraîner (clique pour ouvrir) :

💬 Ré-explique sans regarder
Ré-explique sans regarder

Explique le double tag :main / :sha- et comment le rollback fonctionne sur le VPS.

Une bonne explication couvre : la CI pousse deux tags (:main flottant + :sha- immuable) ; le VPS lit le TAG du .env ; rollback = changer TAG vers un sha sain + docker compose up -d --wait ; les volumes (base de données) ne bougent pas ; les migrations ne sont pas annulées.
🧠 Rappel libre
Rappel libre

La prod casse après un deploy Docker : cite les étapes du retour arrière.

1) Identifier le sha sain (git log --oneline). 2) Changer TAG=sha-SAIN dans le .env. 3) docker compose up -d --wait. Les volumes (base de données) ne bougent pas. Total : ~15 secondes.
⚖️ Juge le conseil de l'IA
Accepter ou rejeter le conseil de l'IA

La prod est cassée. L'IA te dit : "Fais docker compose down puis docker compose up -d avec l'ancienne image, ça repart proprement." Tu acceptes ou tu rejettes ?

À rejeter. docker compose down coupe TOUT : les services applicatifs ET les volumes nommés si mal configurés, ce qui peut effacer des données. La bonne commande est docker compose up -d --wait : elle recrée uniquement les conteneurs dont l'image a changé, laisse les volumes intacts, et attend que le healthcheck soit vert. Jamais de down pour un simple rollback.
À quoi sert le double tag :main / :sha- ?
Qu'est-ce qu'un rollback d'image Docker n'annule PAS ?
Pourquoi utiliser --wait avec docker compose up -d ?
La prod casse après un deploy Docker. Quelle est la bonne séquence de rollback ?
Prochaine étape

Le rollback te sauve d'un bug logique, mais pas d'un disque qui lâche ou d'un serveur incendié. À la leçon 9, on installe le minimum qui sauve vraiment : sauvegardes hors-site automatiques chaque nuit, et une alerte uptime qui te réveille avant tes utilisateurs. Le rollback ne sauve pas tes données, les dumps oui.

Leçon 9 : Sauvegardes et monitoring →
Besoin d'un développeur pour votre projet ?

Réponse sous 24h · Sans engagement