Aller au contenu
← Tous les articles

Infra & DevOps2 min de lecture

Déployer sans coupure avec Docker Compose et Nginx

Pas besoin d’un orchestrateur pour mettre à jour une application sans interrompre ses visiteurs : deux conteneurs, une sonde de santé et un rechargement de Nginx suffisent.

Grille en perspective qui file vers un horizon bleu profond
Sommaire

Sur un serveur unique, la façon la plus courante de déployer est aussi la plus brutale : docker compose up -d arrête l’ancien conteneur avant que le nouveau ne soit prêt. Pendant quelques secondes, parfois bien plus si des migrations tournent, les visiteurs reçoivent une erreur 502.

Cet article décrit une méthode qui tient en quarante lignes de configuration et ne demande ni Kubernetes ni service managé.

Le principe : deux couleurs

L’idée du déploiement bleu/vert est d’avoir deux instances de l’application. Une seule reçoit le trafic ; on met à jour l’autre, on vérifie qu’elle répond, puis on bascule le proxy. Si quelque chose se passe mal, l’ancienne version est toujours là.

Blocs verticaux alignés évoquant les nœuds d’une grappe de serveurs
Le trafic passe par Nginx, qui ne voit qu’une couleur à la fois.

Ce qu’il faut au minimum

  • Une sonde de santé qui dit vraiment si l’application peut servir des requêtes
  • Deux services Compose identiques, à un nom près
  • Un fichier d’amont Nginx que l’on peut réécrire puis recharger

La sonde de santé

Une sonde qui répond toujours 200 ne sert à rien. Elle doit vérifier ce dont chaque requête a besoin : la base de données, et rien de plus coûteux.

TypeScript
export async function GET() {
  try {
    await db.execute(sql`SELECT 1`)
    return Response.json({ status: 'ok' })
  } catch {
    return Response.json({ status: 'degraded' }, { status: 503 })
  }
}

Côté Compose, la sonde laisse le temps aux migrations de s’exécuter grâce à start_period :

YAML
services:
  app-blue: &app
    image: registry.example.com/site:${RELEASE}
    healthcheck:
      test: ['CMD', 'wget', '-qO-', 'http://127.0.0.1:3000/api/health']
      interval: 5s
      timeout: 3s
      start_period: 60s
      retries: 5
  app-green:
    <<: *app

Basculer Nginx

Nginx lit la couleur active dans un petit fichier inclus. Le rechargement (nginx -s reload) termine les connexions en cours avec l’ancienne configuration : aucune requête n’est coupée.

Nginx
# /etc/nginx/conf.d/upstream.conf, réécrit à chaque déploiement
upstream site {
  server 127.0.0.1:3001; # app-green
  keepalive 16;
}

Le script complet

Shell
#!/usr/bin/env bash
set -euo pipefail

active=$(cat .active-color)
next=$([ "$active" = blue ] && echo green || echo blue)

docker compose up -d --wait "app-$next"
sed "s/app-$active/app-$next/" upstream.template > /etc/nginx/conf.d/upstream.conf
nginx -t && nginx -s reload
echo "$next" > .active-color
docker compose stop "app-$active"
Un déploiement réussi est un déploiement dont personne ne s’aperçoit, sauf celui qui lit les journaux.

Les migrations, le vrai piège

Pendant la bascule, les deux versions tournent en même temps sur la même base. Une migration doit donc rester compatible avec la version précédente : on ajoute une colonne avant de l’utiliser, on arrête de l’utiliser avant de la supprimer.

  1. Déploiement 1 : ajouter la colonne, nullable
  2. Déploiement 2 : écrire dans les deux colonnes, lire la nouvelle
  3. Déploiement 3 : supprimer l’ancienne colonne

En vidéo

La démonstration complète, de la branche au serveur, en cinq minutes :

Vidéo de démonstration fictive.

Pour aller plus loin

La documentation de Compose détaille l’option --wait et les conditions de santé ; celle de Nginx, le comportement du rechargement à chaud. Voir aussi notre liste de vérification de déploiement.

  • #Docker
  • #Observabilité

Partager

Un projet, une question ?

Cet article vous a donné une idée, ou vous bloquez sur un problème proche ? Écrivez-moi, je réponds à chaque message.