Docker-Update-Strategie: Container-Updates ohne Ausfallzeit auf einem VPS

11 Min. Lesezeit·Matthieu·containerstraefikzero-downtimedocker-composedocker|

Vier aufeinander aufbauende Methoden zum Aktualisieren von Docker-Containern auf einem VPS, vom einfachen Pull-and-Replace bis zum Blue-Green-Deployment ohne Ausfallzeit mit Traefik. Image-Pinning, Rollback, Diun-Benachrichtigungen und docker-rollout.

Docker-Container auf einem VPS zu aktualisieren muss keine Ausfallzeit bedeuten. Der richtige Ansatz hängt davon ab, was Sie betreiben und wie viel Unterbrechung Sie tolerieren können. Ein persönlicher Blog verträgt ein paar Sekunden Ausfallzeit während eines docker compose up -d. Ein SaaS-Produkt mit zahlenden Kunden nicht.

Diese Anleitung behandelt vier Methoden, von der einfachsten bis zur widerstandsfähigsten. Jede baut auf der vorherigen auf. Beginnen Sie mit der Methode, die zu Ihrer Situation passt, und steigen Sie auf die nächste Stufe um, wenn es nötig wird.

Voraussetzungen: Ein VPS mit Debian 12 oder Ubuntu 24.04 mit Docker Engine 27+ und Docker Compose v2. Alle Befehle verwenden die docker compose-Plugin-Syntax (nicht das veraltete docker-compose-v1-Binary). Docker in Produktion auf einem VPS: Was schiefgeht und wie Sie es beheben

Wie pinne ich Docker-Images auf eine bestimmte Version?

Pinnen Sie Ihre Images auf eine bestimmte Minor- oder Patch-Version in Ihrer Compose-Datei. Der latest-Tag ist ein bewegliches Ziel, das ohne Vorwarnung Breaking Changes einführen kann. Pinning gibt Ihnen Kontrolle darüber, wann Updates stattfinden, und macht Rollbacks möglich, indem das vorherige Image lokal erhalten bleibt.

Verschiedene Tag-Strategien bergen unterschiedliche Risiken:

Tag-Format Beispiel Risikoniveau Update-Verhalten
latest nginx:latest Hoch Beliebige Version, jederzeit. Sie können nicht erkennen, was sich geändert hat.
Nur Major nginx:1 Mittel-hoch Kann von 1.25 auf 1.27 springen. Minor-Versionen können das Verhalten ändern.
Minor nginx:1.27 Niedrig Erhält Patch-Updates (1.27.0 auf 1.27.3). Sicher für die meisten Workloads.
Patch nginx:1.27.3 Sehr niedrig Exakte Version. Keine überraschenden Updates. Sie aktualisieren manuell.
Digest nginx:1.27.3@sha256:6f12... Minimal Byte-identisches Image jedes Mal. Immun gegen Tag-Mutation.

Für die meisten Produktionsdienste pinnen Sie auf die Minor-Version (image: postgres:16.6). Das ist die richtige Balance zwischen Sicherheitspatches und Stabilität. Für Dienste, bei denen Reproduzierbarkeit zählt (CI, regulierte Umgebungen), pinnen Sie auf den vollständigen Digest.

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

Notieren Sie die aktuellen Image-Digests vor dem Update. Sie brauchen sie für ein Rollback:

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

Wie richte ich Health Checks in Docker Compose ein?

Health Checks teilen Docker mit, ob Ihr Container tatsächlich funktioniert, nicht nur ob er läuft. Alle Zero-Downtime-Muster hängen davon ab. Ohne Health Check hat Docker keine Möglichkeit zu wissen, ob der neue Container bereit ist, bevor der alte entfernt wird.

Fügen Sie einen healthcheck-Block zu jedem Service in Ihrer Compose-Datei hinzu. Der test-Befehl wird im angegebenen Intervall innerhalb des Containers ausgeführt. Docker markiert den Container erst als healthy, wenn der Test bestanden ist.

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

Was jedes Feld bewirkt:

  • test: Der auszuführende Befehl. CMD führt ihn direkt aus. Verwenden Sie CMD-SHELL, wenn Sie Shell-Funktionen wie Pipes benötigen.
  • interval: Zeit zwischen den Prüfungen. 15s ist angemessen für Webdienste.
  • timeout: Wie lange auf den Abschluss des Befehls gewartet wird, bevor er als fehlgeschlagen gilt.
  • retries: Anzahl aufeinanderfolgender Fehler, bevor Docker den Container als unhealthy markiert.
  • start_period: Karenzzeit nach dem Container-Start. Health Checks in diesem Zeitfenster zählen nicht zum Fehlerschwellenwert. Setzen Sie den Wert lang genug für den Start Ihrer Anwendung.

Für Dienste, die curl nicht installiert haben, verwenden Sie den vom Dienst angebotenen eingebauten Check:

  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

Prüfen Sie nach dem Start Ihrer Dienste, ob die Health Checks bestanden werden:

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

Der Status (healthy) bedeutet, dass Ihr Health Check konfiguriert ist und besteht. Wenn Sie (health: starting) sehen, befindet sich der Container noch in seiner start_period. Bei (unhealthy) prüfen Sie die Health-Check-Logs:

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

Wie aktualisiere ich einen Docker-Container auf meinem VPS?

Führen Sie docker compose pull aus, um das neue Image zu holen, dann docker compose up -d, um den Container zu ersetzen. Docker Compose stoppt den alten Container, entfernt ihn und startet einen neuen aus dem aktualisierten Image. Das verursacht eine kurze Unterbrechung (typischerweise 2-10 Sekunden), während der neue Container startet und seinen Health Check besteht.

Schritt für Schritt: das einfache Update

Sichern Sie vor jedem Update Ihre Volumes. Ein fehlgeschlagenes Update mit beschädigten Daten ist weitaus schlimmer als ein paar Sekunden Ausfallzeit.

Lesen Sie das Changelog der neuen Version. Prüfen Sie auf Breaking Changes, veraltete Konfigurationsoptionen und erforderliche Migrationsschritte. Das dauert fünf Minuten und spart Stunden beim Debugging.

# Pull the new image
docker compose pull app

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

Das --dry-run-Flag (Docker Compose v2.20+) zeigt, was Compose tun wird, ohne es tatsächlich auszuführen. Sie sehen, welche Container neu erstellt werden:

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

Wenden Sie das Update an:

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

Prüfen Sie, ob der neue Container healthy ist:

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

Testen Sie dann von außerhalb des Servers, ob der Dienst erreichbar ist:

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

Wann sollten Sie Ihre Docker-Container aktualisieren?

Nicht alle Updates haben die gleiche Dringlichkeit. Ein einheitlicher Update-Zeitplan führt entweder zu unnötigem Risiko oder zu verpassten Sicherheitspatches.

  • Sicherheitspatches (CVEs): Sofort anwenden. Abonnieren Sie die Sicherheitshinweise Ihrer Images. Ein bekannter CVE in einem öffentlich zugänglichen Container wird innerhalb von Stunden nach der Veröffentlichung ausgenutzt, nicht Tagen.
  • Patch-Versionen (z. B. 2.4.1 auf 2.4.2): Wöchentlich oder zweiwöchentlich einplanen. Das sind Bugfixes. Changelog lesen, aktualisieren, prüfen.
  • Minor-Versionen (z. B. 2.4 auf 2.5): Monatlich einplanen. Zuerst in einer Staging-Umgebung testen, falls vorhanden. Changelog auf Verhaltensänderungen prüfen.
  • Major-Versionen (z. B. 2.x auf 3.x): Planen und testen. Major-Versionen verursachen Breaking Changes. Migrationsanleitung lesen. Auf einem separaten VPS oder lokal testen, bevor Sie die Produktion anfassen.

Wie mache ich ein Rollback eines Docker-Containers auf ein vorheriges Image?

Docker Compose hat keinen eingebauten Rollback-Befehl. Zum Zurücksetzen: Bearbeiten Sie Ihre Compose-Datei, um den vorherigen Image-Tag oder Digest zu pinnen, und führen Sie dann docker compose up -d aus. Der Container startet mit dem alten Image neu. Das funktioniert nur, wenn Sie das alte Image lokal behalten haben (führen Sie nicht docker image prune direkt nach dem Update aus).

Rollback Schritt für Schritt

Angenommen, Sie haben myapp von 2.4.1 auf 2.5.0 aktualisiert und die neue Version ist fehlerhaft.

  1. Prüfen Sie, ob das alte Image noch lokal verfügbar ist:
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. Bearbeiten Sie Ihre Compose-Datei, um die vorherige Version zu pinnen:
services:
  app:
    image: myapp:2.4.1
  1. Führen Sie das Rollback durch:
docker compose up -d app
[+] Running 1/1Container app-1  Started    0.7s
  1. Prüfen Sie, ob das Rollback funktioniert hat:
docker compose ps app
NAME       IMAGE           STATUS                   PORTS
app        myapp:2.4.1     Up 10 seconds (healthy)  0.0.0.0:8080->8080/tcp

Wenn Sie das alte Image bereits bereinigt haben, lädt Docker es erneut aus der Registry herunter (vorausgesetzt, der Tag existiert noch). Für maximale Sicherheit notieren Sie den vollständigen Digest (sha256:...) vor dem Update. Digests sind unveränderlich. Tags können überschrieben werden.

Automatisierung mit einem Pre-Update-Skript

Speichern Sie den aktuellen Zustand vor jedem Update, damit ein Rollback immer nur einen Befehl entfernt ist:

#!/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

Wird Watchtower 2026 noch gepflegt?

Watchtower wurde am 17. Dezember 2025 archiviert. Die Maintainer verwenden Docker nicht mehr und haben die Entwicklung eingestellt. Die letzte Version ist v1.7.1. Noch wichtiger: Watchtowers Docker SDK verwendet API v1.25, aber Docker Engine 29 hat die minimale API-Version auf v1.44 angehoben. Watchtower ist mit aktuellen Docker-Versionen inkompatibel, es sei denn, Sie senken das API-Minimum manuell mit DOCKER_MIN_API_VERSION=1.25 in Ihrer Daemon-Konfiguration. Das ist ein Workaround, keine Lösung.

Wenn Sie Watchtower heute nutzen, planen Sie die Migration. Für automatisierte Update-Benachrichtigungen ohne automatische Neustarts verwenden Sie Diun. Für automatisierte Updates ohne Ausfallzeit verwenden Sie docker-rollout hinter einem Reverse Proxy.

Wie benachrichtigt Diun Sie über Docker-Image-Updates?

Diun (Docker Image Update Notifier) überwacht Ihre Docker-Registries und sendet Benachrichtigungen, wenn neue Image-Versionen verfügbar sind. Es aktualisiert keine Container. Es informiert Sie, dass ein Update existiert, damit Sie das Changelog lesen und nach eigenem Ermessen aktualisieren können. Das ist der Ansatz „erst informieren, dann handeln".

Fügen Sie Diun zu Ihrer bestehenden Compose-Datei hinzu oder erstellen Sie eine eigene:

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:

Die Slack-Webhook-URL gehört in eine Secrets-Datei, nicht in eine Umgebungsvariable, da Docker Secrets sie aus der docker inspect-Ausgabe und Prozesslistings heraushält. Erstellen Sie die Secrets-Datei mit eingeschränkten Berechtigungen:

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

Die wichtigsten Einstellungen:

  • DIUN_WATCH_SCHEDULE: Cron-Ausdruck. 0 6 * * * prüft täglich um 06:00 Uhr. Passen Sie dies an Ihr Wartungsfenster an.
  • DIUN_PROVIDERS_DOCKER_WATCHBYDEFAULT: Bei true überwacht Diun alle laufenden Container. Setzen Sie es auf false und verwenden Sie Labels für selektive Überwachung.
  • Docker-Socket-Mount: Nur lesend (:ro), da Diun nur Container-Metadaten liest. Es startet oder stoppt niemals Container.

Für selektive Überwachung (empfohlen für größere Stacks) setzen Sie WATCHBYDEFAULT auf false und fügen Sie Labels zu den Containern hinzu, die Sie überwachen möchten:

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

Starten Sie Diun und prüfen Sie die 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

Wenn Diun ein neues Image findet, sendet es eine Slack-Nachricht mit dem Image-Namen, dem aktuellen Tag und dem neuen Tag. Sie entscheiden, ob und wann Sie aktualisieren.

Wie erreicht docker-rollout Updates ohne Ausfallzeit?

docker-rollout ist ein Docker-CLI-Plugin, das Blue-Green-Deployments für Compose-Services durchführt. Es startet einen neuen Container aus dem aktualisierten Image, wartet auf das Bestehen des Health Checks und entfernt dann den alten Container. Der Traffic erreicht nie einen nicht funktionsfähigen Container, da der Reverse Proxy nur zu gesunden Containern routet.

Voraussetzungen:

  • Ein Reverse Proxy (Traefik, Caddy oder nginx-proxy), der den Traffic zu Ihrem Service routet
  • Health Checks in Ihrer Compose-Datei definiert
  • Keine container_name-Direktive am Service (docker-rollout verwaltet Container-Namen)
  • Kein direktes ports-Mapping am Service (der Reverse Proxy übernimmt die Port-Exposition)

docker-rollout installieren

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

Nach der Installation sollte die Version angezeigt werden:

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

Beispiel: Traefik + docker-rollout

Eine minimale Compose-Datei für eine Webanwendung hinter Traefik mit Health Checks. Die App hat weder ports noch container_name, da docker-rollout die Skalierung verwalten muss.

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

Deployment ohne Ausfallzeit

Holen Sie das neue Image und verwenden Sie docker rollout statt 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

Während dieses Vorgangs erkennt Traefik den neuen Container über den Docker-Socket, routet den Traffic zu ihm, sobald er healthy ist, und stoppt das Routing zum alten Container, bevor dieser entfernt wird. Ihre Benutzer bemerken keine Unterbrechung.

Wenn der neue Container seinen Health Check nicht besteht, bricht docker-rollout ab und der alte Container läuft weiter. Kein manuelles Eingreifen nötig.

Was ist Blue-Green-Deployment mit Docker und Traefik?

Beim Blue-Green-Deployment laufen zwei Kopien Ihres Services (Blue und Green). Eine bedient den Live-Traffic, während die andere inaktiv bleibt. Zum Deployen aktualisieren Sie die inaktive Kopie, überprüfen ihre Funktion und leiten dann den Traffic um. Das gibt Ihnen sofortiges Rollback durch Zurückschalten auf die vorherige Kopie.

Das ist das Konzept hinter docker-rollout, aber Sie können es für mehr Kontrolle manuell implementieren. Ein minimales Beispiel mit dynamischer Traefik-Konfiguration:

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

Eine dynamische Traefik-Konfigurationsdatei steuert, welche Kopie den Traffic erhält:

# 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"

Zum Deployen: Aktualisieren Sie das Image von app-green, starten Sie es, warten Sie bis es healthy ist, und bearbeiten Sie dann app.yml, um auf app-green zu zeigen. Traefik übernimmt die Änderung automatisch, da watch=true. Für ein Rollback bearbeiten Sie die Datei so, dass sie wieder auf app-blue zeigt.

Dieser Ansatz bedeutet mehr Aufwand als docker-rollout. Verwenden Sie ihn, wenn Sie explizite Kontrolle über den Wechsel benötigen, Smoke Tests gegen die neue Version durchführen möchten, bevor Sie den Traffic umleiten, oder wenn mehrere Services gleichzeitig wechseln müssen.

Welche Update-Methode sollte ich verwenden?

Wählen Sie die Methode, die zu Ihrer Ausfallzeittoleranz und Infrastrukturkomplexität passt.

Methode Ausfallzeit Komplexität Rollback Reverse Proxy erforderlich Geeignet für
docker compose pull + up 2-10 Sekunden Niedrig Manuell (Compose-Datei bearbeiten) Nein Persönliche Projekte, interne Tools
Diun + manuelles Update Wie oben Niedrig Wie oben Nein Teams, die vor dem Update informiert sein wollen
docker-rollout Keine Mittel Automatisch (bricht bei Fehler ab) Ja Produktionsdienste auf einem einzelnen VPS
Blue-Green (manuell) Keine Hoch Sofort (Konfigurationsdatei umschalten) Ja Multi-Service-Stacks, regulierte Umgebungen

Entscheidungsbaum:

  1. Sind 2-10 Sekunden Ausfallzeit akzeptabel? Verwenden Sie docker compose pull && docker compose up -d.
  2. Möchten Sie über Updates informiert werden, bevor Sie sie anwenden? Fügen Sie Diun hinzu.
  3. Ist Zero Downtime erforderlich? Haben Sie einen Reverse Proxy? Verwenden Sie docker-rollout.
  4. Brauchen Sie explizite Kontrolle über den Traffic-Wechsel? Implementieren Sie Blue-Green manuell.

Etwas funktioniert nicht?

Container startet, zeigt aber (unhealthy)

Prüfen Sie den Health-Check-Befehl. Führen Sie ihn manuell im Container aus:

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

Wenn das fehlschlägt, liegt das Problem in Ihrer Anwendung, nicht in Docker. Prüfen Sie die Anwendungslogs:

docker compose logs app --tail 50

Altes Image wurde bereinigt, Rollback nicht möglich

Wenn der Tag in der Registry noch existiert, lädt docker compose pull es erneut herunter. Wenn Sie per Digest gepinnt haben, lädt Docker das exakte Image unabhängig von Tag-Änderungen:

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

docker-rollout hängt beim Deployment

Der Health Check wird innerhalb des Timeouts nicht bestanden. Prüfen Sie Intervall und Retries des Health Checks. Erhöhen Sie das Timeout:

docker rollout -t 120 app

Watchtower funktioniert nach Docker-Update nicht mehr

Docker Engine 29 erfordert mindestens API v1.44. Watchtower verwendet API v1.25. Migrieren Sie zu Diun für Benachrichtigungen oder docker-rollout für automatisierte Updates ohne Ausfallzeit.

Diun erkennt keine neuen Images

Prüfen Sie den Cron-Zeitplan in DIUN_WATCH_SCHEDULE. Lösen Sie einen manuellen Scan aus:

docker compose exec diun diun image list

Prüfen Sie die Diun-Logs auf Registry-Authentifizierungsfehler:

docker compose logs diun --tail 30