Stratégie de mise à jour Docker : mises à jour sans interruption sur un VPS

13 min de lecture·Matthieu·containerstraefikzero-downtimedocker-composedocker|

Quatre méthodes progressives pour mettre à jour vos conteneurs Docker sur un VPS, du simple pull-and-replace au déploiement blue-green sans interruption avec Traefik. Épinglage d'images, rollback, notifications Diun et docker-rollout.

Mettre à jour des conteneurs Docker sur un VPS ne signifie pas forcément une interruption de service. La bonne approche dépend de ce que vous exécutez et du temps d'arrêt que vous pouvez tolérer. Un blog personnel supporte quelques secondes d'interruption pendant un docker compose up -d. Un produit SaaS avec des clients payants, non.

Ce guide couvre quatre méthodes, de la plus simple à la plus résiliente. Chacune s'appuie sur la précédente. Commencez par celle qui correspond à votre situation et passez au niveau supérieur quand le besoin s'en fait sentir.

Prérequis : Un VPS sous Debian 12 ou Ubuntu 24.04 avec Docker Engine 27+ et Docker Compose v2 installés. Toutes les commandes utilisent la syntaxe du plugin docker compose (pas l'ancien binaire docker-compose v1). Docker en production sur un VPS : ce qui casse et comment corriger

Comment épingler les images Docker à une version précise ?

Épinglez vos images à une version mineure ou patch dans votre fichier compose. Le tag latest est une cible mouvante qui peut introduire des changements cassants sans prévenir. L'épinglage vous donne le contrôle sur le moment des mises à jour et rend le rollback possible en conservant l'image précédente localement.

Différentes stratégies de tags comportent différents niveaux de risque :

Format du tag Exemple Niveau de risque Comportement de mise à jour
latest nginx:latest Élevé N'importe quelle version, n'importe quand. Impossible de savoir ce qui a changé.
Majeure seule nginx:1 Moyen-élevé Peut passer de 1.25 à 1.27. Les versions mineures peuvent changer le comportement.
Mineure nginx:1.27 Faible Reçoit les patchs (1.27.0 à 1.27.3). Sûr pour la plupart des workloads.
Patch nginx:1.27.3 Très faible Version exacte. Pas de mise à jour surprise. Vous mettez à jour manuellement.
Digest nginx:1.27.3@sha256:6f12... Minimal Image identique octet par octet à chaque fois. Immunisé contre la mutation de tags.

Pour la plupart des services en production, épinglez à la version mineure (image: postgres:16.6). C'est le bon équilibre entre patchs de sécurité et stabilité. Pour les services où la reproductibilité compte (CI, environnements réglementés), épinglez au digest complet.

services:
  app:
    image: myapp:2.4.1
    # Not: image: myapp:latest
  db:
    image: postgres:16.6

Enregistrez les digests de vos images actuelles avant de mettre à jour. Vous en aurez besoin pour un rollback :

docker image inspect --format='{{index .RepoDigests 0}}' $(docker compose images app -q)
myapp@sha256:a1b2c3d4e5f6...

Comment configurer les health checks dans Docker Compose ?

Les health checks indiquent à Docker si votre conteneur fonctionne réellement, pas seulement s'il tourne. Tous les patterns de mise à jour sans interruption en dépendent. Sans health check, Docker n'a aucun moyen de savoir si le nouveau conteneur est prêt avant de supprimer l'ancien.

Ajoutez un bloc healthcheck à chaque service dans votre fichier compose. La commande test s'exécute à l'intérieur du conteneur à l'intervalle spécifié. Docker marque le conteneur comme healthy uniquement après la réussite du test.

services:
  app:
    image: myapp:2.4.1
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 15s
      timeout: 5s
      retries: 3
      start_period: 30s

Rôle de chaque champ :

  • test : La commande à exécuter. CMD l'exécute directement. Utilisez CMD-SHELL si vous avez besoin de fonctionnalités shell comme les pipes.
  • interval : Temps entre les vérifications. 15s est raisonnable pour les services web.
  • timeout : Temps d'attente avant de considérer la commande comme échouée.
  • retries : Nombre d'échecs consécutifs avant que Docker marque le conteneur comme unhealthy.
  • start_period : Période de grâce après le démarrage du conteneur. Les health checks pendant cette fenêtre ne comptent pas dans le seuil d'échec. Réglez-la suffisamment longue pour le démarrage de votre application.

Pour les services qui n'ont pas curl installé, utilisez le check natif proposé par le service :

  db:
    image: postgres:16.6
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U myuser -d mydb"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 20s

  cache:
    image: redis:7.4
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 3

Après le démarrage de vos services, vérifiez que les health checks passent :

docker compose ps
NAME       IMAGE           STATUS                    PORTS
app        myapp:2.4.1     Up 2 minutes (healthy)    0.0.0.0:8080->8080/tcp
db         postgres:16.6   Up 2 minutes (healthy)    5432/tcp

Le statut (healthy) signifie que votre health check est configuré et qu'il passe. Si vous voyez (health: starting), le conteneur est encore dans sa start_period. Si (unhealthy), consultez les logs du health check :

docker inspect --format='{{json .State.Health}}' $(docker compose ps -q app) | python3 -m json.tool

Comment mettre à jour un conteneur Docker sur mon VPS ?

Exécutez docker compose pull pour récupérer la nouvelle image, puis docker compose up -d pour remplacer le conteneur. Docker Compose arrête l'ancien conteneur, le supprime et en démarre un nouveau à partir de l'image mise à jour. Cela provoque une brève interruption (généralement 2 à 10 secondes) pendant que le nouveau conteneur démarre et passe son health check.

Étape par étape : la mise à jour simple

Avant toute mise à jour, sauvegardez vos volumes. Une mise à jour ratée avec des données corrompues est bien pire que quelques secondes d'interruption.

Lisez le changelog de la nouvelle version. Vérifiez les changements cassants, les options de configuration dépréciées et les étapes de migration requises. Ça prend cinq minutes et ça vous évite des heures de débogage.

# Pull the new image
docker compose pull app

# Check what changed
docker compose up -d --dry-run

Le flag --dry-run (Docker Compose v2.20+) montre ce que Compose va faire sans réellement le faire. Vous verrez quels conteneurs seront recréés :

DRY RUN MODE - service "app" - Pull
DRY RUN MODE - Container app-1 - Recreate
DRY RUN MODE - Container app-1 - Started

Appliquez la mise à jour :

docker compose up -d app
[+] Running 1/1Container app-1  Started    0.8s

Vérifiez que le nouveau conteneur est healthy :

docker compose ps app
NAME       IMAGE           STATUS                    PORTS
app        myapp:2.5.0     Up 15 seconds (healthy)   0.0.0.0:8080->8080/tcp

Puis testez depuis l'extérieur du serveur pour vous assurer que le service est accessible :

curl -s -o /dev/null -w "%{http_code}" https://app.example.com/health
200

Quand mettre à jour vos conteneurs Docker ?

Toutes les mises à jour n'ont pas la même urgence. Un calendrier de mises à jour uniforme mène soit à des risques inutiles, soit à des patchs de sécurité manqués.

  • Patchs de sécurité (CVE) : Appliquez-les immédiatement. Abonnez-vous aux avis de sécurité de vos images. Un CVE connu dans un conteneur exposé publiquement est exploité en quelques heures après sa divulgation, pas en quelques jours.
  • Versions patch (ex. : 2.4.1 à 2.4.2) : Planifiez-les chaque semaine ou toutes les deux semaines. Ce sont des correctifs. Lisez le changelog, mettez à jour, vérifiez.
  • Versions mineures (ex. : 2.4 à 2.5) : Planifiez-les mensuellement. Testez d'abord dans un environnement de staging si vous en avez un. Revoyez le changelog pour les changements de comportement.
  • Versions majeures (ex. : 2.x à 3.x) : Planifiez et testez. Les versions majeures cassent des choses. Lisez le guide de migration. Testez sur un VPS séparé ou en local avant de toucher à la production.

Comment revenir à une image Docker précédente ?

Docker Compose n'a pas de commande de rollback intégrée. Pour revenir en arrière : éditez votre fichier compose pour épingler le tag ou digest de l'image précédente, puis exécutez docker compose up -d. Le conteneur redémarre avec l'ancienne image. Cela fonctionne uniquement si vous avez conservé l'ancienne image localement (n'exécutez pas docker image prune juste après la mise à jour).

Rollback étape par étape

Supposons que vous avez mis à jour myapp de 2.4.1 à 2.5.0 et que la nouvelle version est cassée.

  1. Vérifiez que l'ancienne image est toujours disponible localement :
docker images myapp
REPOSITORY   TAG     IMAGE ID       CREATED        SIZE
myapp        2.5.0   abc123def456   2 hours ago    185MB
myapp        2.4.1   789fed654cba   2 weeks ago    182MB
  1. Éditez votre fichier compose pour épingler la version précédente :
services:
  app:
    image: myapp:2.4.1
  1. Effectuez le rollback :
docker compose up -d app
[+] Running 1/1Container app-1  Started    0.7s
  1. Vérifiez que le rollback a fonctionné :
docker compose ps app
NAME       IMAGE           STATUS                   PORTS
app        myapp:2.4.1     Up 10 seconds (healthy)  0.0.0.0:8080->8080/tcp

Si vous avez déjà purgé l'ancienne image, Docker la récupérera depuis le registre (en supposant que le tag existe toujours). Pour une sécurité maximale, notez le digest complet (sha256:...) avant de mettre à jour. Les digests sont immuables. Les tags peuvent être écrasés.

Automatisez avec un script de pré-mise à jour

Sauvegardez l'état actuel avant chaque mise à jour pour qu'un rollback soit toujours à une commande :

#!/bin/bash
# save-state.sh - Run before every update
COMPOSE_FILE="${1:-docker-compose.yml}"
DATE=$(date +%Y%m%d-%H%M%S)
BACKUP_DIR="./rollback/${DATE}"

mkdir -p "${BACKUP_DIR}"
cp "${COMPOSE_FILE}" "${BACKUP_DIR}/"
docker compose ps --format json > "${BACKUP_DIR}/containers.json"
docker compose images --format json > "${BACKUP_DIR}/images.json"

echo "State saved to ${BACKUP_DIR}"
chmod 700 save-state.sh

Watchtower est-il toujours maintenu en 2026 ?

Watchtower a été archivé le 17 décembre 2025. Les mainteneurs n'utilisent plus Docker et ont arrêté le développement. La dernière version est la v1.7.1. Plus important encore, le SDK Docker de Watchtower utilise l'API v1.25, mais Docker Engine 29 a relevé la version minimale de l'API à v1.44. Watchtower est incompatible avec les versions actuelles de Docker, sauf si vous abaissez manuellement le minimum de l'API avec DOCKER_MIN_API_VERSION=1.25 dans la configuration de votre daemon. C'est un contournement, pas une solution.

Si vous utilisez Watchtower aujourd'hui, prévoyez de migrer. Pour des notifications de mises à jour automatisées sans redémarrage automatique, utilisez Diun. Pour des mises à jour automatisées sans interruption, utilisez docker-rollout derrière un reverse proxy.

Comment Diun vous notifie-t-il des mises à jour d'images Docker ?

Diun (Docker Image Update Notifier) surveille vos registres Docker et envoie des notifications quand de nouvelles versions d'images sont disponibles. Il ne met pas à jour les conteneurs. Il vous informe qu'une mise à jour existe pour que vous puissiez lire le changelog et mettre à jour selon vos propres termes. C'est l'approche « savoir avant d'agir ».

Ajoutez Diun à votre fichier compose existant ou créez-en un dédié :

services:
  diun:
    image: crazymax/diun:4
    command: serve
    volumes:
      - "diun-data:/data"
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
    environment:
      TZ: "Europe/Berlin"
      DIUN_WATCH_WORKERS: "10"
      DIUN_WATCH_SCHEDULE: "0 6 * * *"
      DIUN_PROVIDERS_DOCKER: "true"
      DIUN_PROVIDERS_DOCKER_WATCHBYDEFAULT: "true"
      DIUN_NOTIF_SLACK_WEBHOOKURL_FILE: "/run/secrets/slack_webhook"
    secrets:
      - slack_webhook
    restart: unless-stopped

secrets:
  slack_webhook:
    file: ./secrets/slack_webhook.txt

volumes:
  diun-data:

L'URL du webhook Slack va dans un fichier de secrets, pas dans une variable d'environnement, car Docker secrets la garde en dehors de la sortie de docker inspect et des listings de processus. Créez le fichier de secrets avec des permissions restreintes :

mkdir -p secrets
echo "https://hooks.slack.com/services/YOUR/WEBHOOK/URL" > secrets/slack_webhook.txt
chmod 600 secrets/slack_webhook.txt

Paramètres principaux :

  • DIUN_WATCH_SCHEDULE : Expression cron. 0 6 * * * vérifie tous les jours à 06h00. Ajustez selon votre fenêtre de maintenance.
  • DIUN_PROVIDERS_DOCKER_WATCHBYDEFAULT : Quand true, Diun surveille tous les conteneurs en cours d'exécution. Mettez à false et utilisez des labels pour une surveillance sélective.
  • Montage du socket Docker : En lecture seule (:ro) car Diun ne lit que les métadonnées des conteneurs. Il ne démarre ni n'arrête jamais de conteneurs.

Pour une surveillance sélective (recommandé pour les stacks plus importants), mettez WATCHBYDEFAULT à false et ajoutez des labels aux conteneurs que vous souhaitez surveiller :

services:
  app:
    image: myapp:2.4.1
    labels:
      - "diun.enable=true"
      - "diun.watch_repo=true"

Démarrez Diun et consultez les logs :

docker compose up -d diun
docker compose logs diun --tail 20
diun  | Thu, 19 Mar 2026 06:00:01 CET INF Starting Diun version=v4.31.0
diun  | Thu, 19 Mar 2026 06:00:01 CET INF Configuration loaded from 5 environment variable(s)
diun  | Thu, 19 Mar 2026 06:00:02 CET INF Cron triggered
diun  | Thu, 19 Mar 2026 06:00:03 CET INF New image found image=docker.io/myapp:2.5.0 provider=docker

Quand Diun détecte une nouvelle image, il envoie un message Slack avec le nom de l'image, le tag actuel et le nouveau tag. C'est vous qui décidez si et quand mettre à jour.

Comment docker-rollout permet-il des mises à jour sans interruption ?

docker-rollout est un plugin CLI Docker qui effectue des déploiements blue-green pour les services Compose. Il démarre un nouveau conteneur à partir de l'image mise à jour, attend que le health check passe, puis supprime l'ancien conteneur. Le trafic n'atteint jamais un conteneur non opérationnel car le reverse proxy ne route que vers les conteneurs healthy.

Prérequis :

  • Un reverse proxy (Traefik, Caddy ou nginx-proxy) qui route le trafic vers votre service
  • Des health checks définis dans votre fichier compose
  • Pas de directive container_name sur le service (docker-rollout gère les noms de conteneurs)
  • Pas de mapping ports direct sur le service (le reverse proxy gère l'exposition des ports)

Installer docker-rollout

mkdir -p /usr/local/lib/docker/cli-plugins
curl -fsSL https://raw.githubusercontent.com/wowu/docker-rollout/main/docker-rollout \
  -o /usr/local/lib/docker/cli-plugins/docker-rollout
chmod +x /usr/local/lib/docker/cli-plugins/docker-rollout

La version devrait s'afficher une fois installé :

docker rollout --version
docker-rollout version v0.13

Exemple : Traefik + docker-rollout

Un fichier compose minimal pour une application web derrière Traefik avec des health checks. L'application n'a ni ports ni container_name car docker-rollout doit gérer le scaling.

services:
  traefik:
    image: traefik:3.3
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
    ports:
      - "80:80"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
    restart: unless-stopped

  app:
    image: myapp:2.4.1
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.app.rule=Host(`app.example.com`)"
      - "traefik.http.routers.app.entrypoints=web"
      - "traefik.http.services.app.loadbalancer.server.port=8080"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 15s
      timeout: 5s
      retries: 3
      start_period: 30s
    restart: unless-stopped

Déployer sans interruption

Récupérez la nouvelle image, puis utilisez docker rollout au lieu de docker compose up -d :

docker compose pull app
docker rollout app
==> Scaling 'app' to '2' instances
 Container myproject-app-2 Creating
 Container myproject-app-2 Created
 Container myproject-app-2 Starting
 Container myproject-app-2 Started
==> Waiting for new containers to be healthy (timeout: 60 seconds)
==> Stopping and removing old containers

Pendant ce processus, Traefik détecte le nouveau conteneur via le socket Docker, route le trafic vers lui une fois qu'il est healthy et arrête de router vers l'ancien conteneur avant sa suppression. Vos utilisateurs ne voient aucune interruption.

Si le nouveau conteneur échoue à son health check, docker-rollout annule l'opération et l'ancien conteneur continue de tourner. Aucune intervention manuelle nécessaire.

Qu'est-ce que le déploiement blue-green avec Docker et Traefik ?

Le déploiement blue-green exécute deux copies de votre service (blue et green). L'une sert le trafic en direct pendant que l'autre reste inactive. Pour déployer, vous mettez à jour la copie inactive, vérifiez qu'elle fonctionne, puis basculez le trafic. Cela vous donne un rollback instantané en rebasculant vers la copie précédente.

C'est le concept derrière docker-rollout, mais vous pouvez l'implémenter manuellement pour plus de contrôle. Un exemple minimal utilisant la configuration dynamique de Traefik :

services:
  traefik:
    image: traefik:3.3
    command:
      - "--providers.file.directory=/etc/traefik/dynamic"
      - "--providers.file.watch=true"
      - "--entrypoints.web.address=:80"
    ports:
      - "80:80"
    volumes:
      - "./traefik/dynamic:/etc/traefik/dynamic:ro"
    restart: unless-stopped

  app-blue:
    image: myapp:2.4.1
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 15s
      timeout: 5s
      retries: 3
      start_period: 30s

  app-green:
    image: myapp:2.4.1
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 15s
      timeout: 5s
      retries: 3
      start_period: 30s

Un fichier de configuration dynamique Traefik contrôle quelle copie reçoit le trafic :

# traefik/dynamic/app.yml
http:
  routers:
    app:
      rule: "Host(`app.example.com`)"
      service: app
      entryPoints:
        - web
  services:
    app:
      loadBalancer:
        servers:
          - url: "http://app-blue:8080"

Pour déployer : mettez à jour l'image de app-green, démarrez-le, attendez qu'il soit healthy, puis éditez app.yml pour pointer vers app-green. Traefik prend le changement en compte automatiquement car watch=true. Pour revenir en arrière, éditez le fichier pour pointer à nouveau vers app-blue.

Cette approche demande plus de travail que docker-rollout. Utilisez-la quand vous avez besoin d'un contrôle explicite sur la bascule, quand vous voulez exécuter des smoke tests sur la nouvelle version avant de basculer le trafic, ou quand plusieurs services doivent basculer ensemble.

Quelle méthode de mise à jour choisir ?

Choisissez la méthode qui correspond à votre tolérance aux interruptions et à la complexité de votre infrastructure.

Méthode Interruption Complexité Rollback Reverse proxy requis Adapté pour
docker compose pull + up 2-10 secondes Faible Manuel (éditer le fichier compose) Non Projets personnels, outils internes
Diun + mise à jour manuelle Idem Faible Idem Non Équipes qui veulent de la visibilité avant de mettre à jour
docker-rollout Aucune Moyenne Automatique (annule en cas d'échec) Oui Services de production sur un VPS unique
Blue-green (manuel) Aucune Élevée Instantané (bascule du fichier de config) Oui Stacks multi-services, environnements réglementés

Arbre de décision :

  1. Est-ce que 2 à 10 secondes d'interruption sont acceptables ? Utilisez docker compose pull && docker compose up -d.
  2. Voulez-vous être informé des mises à jour avant de les appliquer ? Ajoutez Diun.
  3. Le zéro interruption est-il requis ? Avez-vous un reverse proxy ? Utilisez docker-rollout.
  4. Vous avez besoin d'un contrôle explicite sur la bascule de trafic ? Implémentez le blue-green manuellement.

Quelque chose ne fonctionne pas ?

Le conteneur démarre mais affiche (unhealthy)

Vérifiez la commande du health check. Exécutez-la manuellement dans le conteneur :

docker compose exec app curl -f http://localhost:8080/health

Si ça échoue, le problème vient de votre application, pas de Docker. Consultez les logs applicatifs :

docker compose logs app --tail 50

L'ancienne image a été purgée, impossible de revenir en arrière

Si le tag existe toujours dans le registre, docker compose pull la récupérera. Si vous avez épinglé par digest, Docker récupère l'image exacte indépendamment des changements de tags :

image: myapp:2.4.1@sha256:789fed654cba...

docker-rollout se bloque pendant le déploiement

Le health check ne passe pas dans le délai imparti. Vérifiez l'intervalle et les retries du health check. Augmentez le timeout :

docker rollout -t 120 app

Watchtower ne fonctionne plus après une mise à jour Docker

Docker Engine 29 requiert l'API v1.44 au minimum. Watchtower utilise l'API v1.25. Migrez vers Diun pour les notifications ou docker-rollout pour les mises à jour automatisées sans interruption.

Diun ne détecte pas les nouvelles images

Vérifiez le planning cron dans DIUN_WATCH_SCHEDULE. Lancez un scan manuel :

docker compose exec diun diun image list

Consultez les logs de Diun pour les erreurs d'authentification au registre :

docker compose logs diun --tail 30

Copyright 2026 Virtua.Cloud. Tous droits réservés. Ce contenu est une création originale de l'équipe Virtua.Cloud. Toute reproduction, republication ou redistribution sans autorisation écrite est interdite.

Prêt à essayer ?

Déployez votre serveur en quelques secondes. Linux, Windows ou FreeBSD.

Voir les offres VPS