Docker-updatestrategie: containerupdates zonder downtime op een VPS

11 min leestijd·Matthieu·containerstraefikzero-downtimedocker-composedocker|

Vier oplopende methoden om Docker-containers op een VPS bij te werken, van simpel pull-and-replace tot zero-downtime blue-green deployments met Traefik. Image pinning, rollback, Diun-notificaties en docker-rollout.

Docker-containers updaten op een VPS hoeft geen downtime te betekenen. De juiste aanpak hangt af van wat je draait en hoeveel onderbreking je kunt tolereren. Een persoonlijke blog kan een paar seconden downtime aan tijdens een docker compose up -d. Een SaaS-product met betalende klanten niet.

Deze handleiding behandelt vier methoden, van de eenvoudigste tot de meest veerkrachtige. Elke methode bouwt voort op de vorige. Begin met wat past bij jouw situatie en stap over naar het volgende niveau wanneer dat nodig is.

Vereisten: Een VPS met Debian 12 of Ubuntu 24.04 met Docker Engine 27+ en Docker Compose v2 geïnstalleerd. Alle commando's gebruiken de docker compose-pluginsyntax (niet het verouderde docker-compose v1-binary). Docker in productie op een VPS: wat er misgaat en hoe je het oplost

Hoe pin ik Docker-images op een specifieke versie?

Pin je images op een specifieke minor- of patchversie in je compose-bestand. De latest-tag is een bewegend doelwit dat zonder waarschuwing breaking changes kan introduceren. Pinning geeft je controle over wanneer updates plaatsvinden en maakt rollback mogelijk door de vorige image lokaal te bewaren.

Verschillende tagstrategieën brengen verschillende risico's met zich mee:

Tagformaat Voorbeeld Risiconiveau Updategedrag
latest nginx:latest Hoog Elke versie, op elk moment. Je kunt niet zien wat er veranderd is.
Alleen major nginx:1 Middel-hoog Kan van 1.25 naar 1.27 springen. Minorversies kunnen gedrag wijzigen.
Minor nginx:1.27 Laag Ontvangt patchupdates (1.27.0 naar 1.27.3). Veilig voor de meeste workloads.
Patch nginx:1.27.3 Zeer laag Exacte versie. Geen verrassende updates. Je update handmatig.
Digest nginx:1.27.3@sha256:6f12... Minimaal Byte-identieke image elke keer. Immuun voor tagmutatie.

Voor de meeste productieservices, pin op de minorversie (image: postgres:16.6). Dit is de juiste balans tussen beveiligingspatches en stabiliteit. Voor services waar reproduceerbaarheid telt (CI, gereguleerde omgevingen), pin op de volledige digest.

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

Noteer de huidige image-digests voordat je update. Je hebt ze nodig voor een rollback:

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

Hoe stel ik health checks in Docker Compose in?

Health checks vertellen Docker of je container daadwerkelijk werkt, niet alleen of hij draait. Alle zero-downtime-patronen zijn ervan afhankelijk. Zonder health check heeft Docker geen manier om te weten of de nieuwe container klaar is voordat de oude wordt verwijderd.

Voeg een healthcheck-blok toe aan elke service in je compose-bestand. Het test-commando wordt op het opgegeven interval binnen de container uitgevoerd. Docker markeert de container pas als healthy nadat de test slaagt.

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

Wat elk veld doet:

  • test: Het uit te voeren commando. CMD voert het direct uit. Gebruik CMD-SHELL als je shellfuncties zoals pipes nodig hebt.
  • interval: Tijd tussen de controles. 15s is redelijk voor webservices.
  • timeout: Hoe lang te wachten tot het commando klaar is voordat het als mislukt wordt beschouwd.
  • retries: Aantal opeenvolgende mislukkingen voordat Docker de container als unhealthy markeert.
  • start_period: Gratieperiode na het starten van de container. Health checks tijdens dit venster tellen niet mee voor de faaldrempel. Stel het lang genoeg in voor het opstarten van je applicatie.

Voor services die curl niet geïnstalleerd hebben, gebruik de ingebouwde controle die de service biedt:

  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

Controleer na het starten van je services of de health checks slagen:

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

De status (healthy) betekent dat je health check geconfigureerd is en slaagt. Als je (health: starting) ziet, bevindt de container zich nog in zijn start_period. Bij (unhealthy), bekijk de health check-logs:

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

Hoe update ik een Docker-container op mijn VPS?

Voer docker compose pull uit om de nieuwe image op te halen, vervolgens docker compose up -d om de container te vervangen. Docker Compose stopt de oude container, verwijdert deze en start een nieuwe vanuit de bijgewerkte image. Dit veroorzaakt een korte onderbreking (meestal 2-10 seconden) terwijl de nieuwe container opstart en zijn health check doorstaat.

Stap voor stap: de simpele update

Maak vóór elke update een backup van je volumes. Een mislukte update met beschadigde data is veel erger dan een paar seconden downtime.

Lees de changelog van de nieuwe versie. Controleer op breaking changes, verouderde configuratie-opties en vereiste migratiestappen. Dit kost vijf minuten en bespaart uren debuggen.

# Pull the new image
docker compose pull app

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

De --dry-run-vlag (Docker Compose v2.20+) toont wat Compose gaat doen zonder het daadwerkelijk uit te voeren. Je ziet welke containers opnieuw worden aangemaakt:

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

Pas de update toe:

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

Controleer of de nieuwe container healthy is:

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

Test vervolgens van buiten de server om te controleren of de service bereikbaar is:

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

Wanneer je Docker-containers updaten?

Niet alle updates hebben dezelfde urgentie. Een uniform updateschema leidt tot onnodig risico of gemiste beveiligingspatches.

  • Beveiligingspatches (CVE's): Pas direct toe. Abonneer je op de beveiligingsmeldingen van je images. Een bekende CVE in een publiek toegankelijke container wordt binnen uren na publicatie misbruikt, niet dagen.
  • Patchversies (bijv. 2.4.1 naar 2.4.2): Plan wekelijks of tweewekelijks. Dit zijn bugfixes. Lees de changelog, update, controleer.
  • Minorversies (bijv. 2.4 naar 2.5): Plan maandelijks. Test eerst in een stagingomgeving als je die hebt. Bekijk de changelog op gedragswijzigingen.
  • Majorversies (bijv. 2.x naar 3.x): Plan en test. Majorversies breken dingen. Lees de migratiegids. Test op een aparte VPS of lokaal voordat je productie aanraakt.

Hoe doe ik een rollback van een Docker-container naar een vorige image?

Docker Compose heeft geen ingebouwd rollback-commando. Om terug te gaan: bewerk je compose-bestand om de vorige image-tag of digest te pinnen en voer dan docker compose up -d uit. De container herstart met de oude image. Dit werkt alleen als je de oude image lokaal hebt bewaard (voer niet direct na het updaten docker image prune uit).

Stapsgewijze rollback

Stel dat je myapp hebt geüpdatet van 2.4.1 naar 2.5.0 en de nieuwe versie kapot is.

  1. Controleer of de oude image nog lokaal beschikbaar is:
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. Bewerk je compose-bestand om de vorige versie te pinnen:
services:
  app:
    image: myapp:2.4.1
  1. Voer de rollback uit:
docker compose up -d app
[+] Running 1/1Container app-1  Started    0.7s
  1. Controleer of de rollback gelukt is:
docker compose ps app
NAME       IMAGE           STATUS                   PORTS
app        myapp:2.4.1     Up 10 seconds (healthy)  0.0.0.0:8080->8080/tcp

Als je de oude image al hebt opgeruimd, haalt Docker deze opnieuw op uit de registry (ervan uitgaande dat de tag nog bestaat). Voor maximale veiligheid, noteer de volledige digest (sha256:...) vóór het updaten. Digests zijn onveranderlijk. Tags kunnen worden overschreven.

Automatiseer met een pre-updatescript

Sla de huidige status op vóór elke update zodat een rollback altijd één commando verwijderd is:

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

Wordt Watchtower nog onderhouden in 2026?

Watchtower is gearchiveerd op 17 december 2025. De maintainers gebruiken Docker niet meer en zijn gestopt met de ontwikkeling. De laatste release is v1.7.1. Belangrijker nog: Watchtowers Docker SDK gebruikt API v1.25, maar Docker Engine 29 heeft de minimale API-versie verhoogd naar v1.44. Watchtower is incompatibel met huidige Docker-versies, tenzij je het API-minimum handmatig verlaagt met DOCKER_MIN_API_VERSION=1.25 in je daemonconfiguratie. Dat is een workaround, geen oplossing.

Als je Watchtower vandaag gebruikt, plan dan de migratie. Voor geautomatiseerde update-notificaties zonder automatische herstarts, gebruik Diun. Voor geautomatiseerde updates zonder downtime, gebruik docker-rollout achter een reverse proxy.

Hoe waarschuwt Diun je over Docker-image-updates?

Diun (Docker Image Update Notifier) bewaakt je Docker-registries en stuurt notificaties wanneer er nieuwe imageversies beschikbaar zijn. Het update geen containers. Het vertelt je dat er een update bestaat zodat je de changelog kunt lezen en op je eigen voorwaarden kunt updaten. Dit is de aanpak van «eerst weten, dan handelen».

Voeg Diun toe aan je bestaande compose-bestand of maak een apart bestand:

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:

De Slack-webhook-URL hoort in een secretsbestand, niet in een omgevingsvariabele, omdat Docker secrets het buiten de docker inspect-output en proceslijsten houdt. Maak het secretsbestand met beperkte rechten:

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

Belangrijkste instellingen:

  • DIUN_WATCH_SCHEDULE: Cron-expressie. 0 6 * * * controleert dagelijks om 06:00 uur. Pas aan naar jouw onderhoudsvenster.
  • DIUN_PROVIDERS_DOCKER_WATCHBYDEFAULT: Bij true bewaakt Diun alle draaiende containers. Zet op false en gebruik labels voor selectieve bewaking.
  • Docker-socketmount: Alleen-lezen (:ro) omdat Diun alleen containermetadata leest. Het start of stopt nooit containers.

Voor selectieve bewaking (aanbevolen voor grotere stacks), zet WATCHBYDEFAULT op false en voeg labels toe aan de containers die je wilt bewaken:

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

Start Diun en bekijk de 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

Wanneer Diun een nieuwe image vindt, stuurt het een Slack-bericht met de imagenaam, de huidige tag en de nieuwe tag. Jij beslist of en wanneer je update.

Hoe bereikt docker-rollout updates zonder downtime?

docker-rollout is een Docker CLI-plugin die blue-green deployments uitvoert voor Compose-services. Het start een nieuwe container vanuit de bijgewerkte image, wacht tot de health check slaagt en verwijdert vervolgens de oude container. Verkeer bereikt nooit een niet-werkende container omdat de reverse proxy alleen naar gezonde containers routeert.

Vereisten:

  • Een reverse proxy (Traefik, Caddy of nginx-proxy) die verkeer naar je service routeert
  • Health checks gedefinieerd in je compose-bestand
  • Geen container_name-directive op de service (docker-rollout beheert containernamen)
  • Geen directe ports-mapping op de service (de reverse proxy regelt de portexpositie)

docker-rollout installeren

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

Na installatie zou de versie moeten verschijnen:

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

Voorbeeld: Traefik + docker-rollout

Een minimaal compose-bestand voor een webapp achter Traefik met health checks. De app heeft geen ports of container_name omdat docker-rollout het schalen moet beheren.

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

Deployen zonder downtime

Haal de nieuwe image op en gebruik docker rollout in plaats van 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

Tijdens dit proces detecteert Traefik de nieuwe container via de Docker-socket, routeert verkeer ernaar zodra deze healthy is en stopt met routeren naar de oude container voordat deze wordt verwijderd. Je gebruikers merken geen onderbreking.

Als de nieuwe container zijn health check niet doorstaat, breekt docker-rollout af en blijft de oude container draaien. Geen handmatige interventie nodig.

Wat is blue-green deployment met Docker en Traefik?

Blue-green deployment draait twee kopieën van je service (blue en green). Eén bedient het live verkeer terwijl de andere inactief blijft. Om te deployen update je de inactieve kopie, controleer je of deze werkt en schakel je het verkeer om. Dit geeft je directe rollback door terug te schakelen naar de vorige kopie.

Dit is het concept achter docker-rollout, maar je kunt het handmatig implementeren voor meer controle. Een minimaal voorbeeld met dynamische Traefik-configuratie:

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

Een dynamisch Traefik-configuratiebestand bepaalt welke kopie het verkeer ontvangt:

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

Om te deployen: update de image van app-green, start deze, wacht tot deze healthy is en bewerk dan app.yml om naar app-green te wijzen. Traefik pikt de wijziging automatisch op omdat watch=true. Om terug te rollen, bewerk het bestand om weer naar app-blue te wijzen.

Deze aanpak is meer werk dan docker-rollout. Gebruik het wanneer je expliciete controle over de switch nodig hebt, smoke tests wilt uitvoeren tegen de nieuwe versie voordat je verkeer omschakelt, of wanneer meerdere services tegelijk moeten schakelen.

Welke updatemethode moet ik gebruiken?

Kies de methode die past bij je downtimetolerantie en infrastructuurcomplexiteit.

Methode Downtime Complexiteit Rollback Reverse proxy vereist Geschikt voor
docker compose pull + up 2-10 seconden Laag Handmatig (compose-bestand bewerken) Nee Persoonlijke projecten, interne tools
Diun + handmatige update Idem Laag Idem Nee Teams die zichtbaarheid willen vóór het updaten
docker-rollout Geen Middel Automatisch (breekt af bij falen) Ja Productieservices op een enkele VPS
Blue-green (handmatig) Geen Hoog Direct (configuratiebestand omschakelen) Ja Multi-servicestacks, gereguleerde omgevingen

Beslisboom:

  1. Is 2-10 seconden downtime acceptabel? Gebruik docker compose pull && docker compose up -d.
  2. Wil je over updates weten vóór je ze toepast? Voeg Diun toe.
  3. Is zero downtime vereist? Heb je een reverse proxy? Gebruik docker-rollout.
  4. Heb je expliciete controle over de verkeersswitch nodig? Implementeer blue-green handmatig.

Iets gaat niet goed?

Container start maar toont (unhealthy)

Controleer het health check-commando. Voer het handmatig uit in de container:

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

Als dit faalt, zit het probleem in je applicatie, niet in Docker. Bekijk de applicatielogs:

docker compose logs app --tail 50

Oude image is opgeruimd, kan niet terugdraaien

Als de tag nog in de registry bestaat, haalt docker compose pull deze op. Als je op digest hebt gepind, haalt Docker de exacte image op ongeacht tagwijzigingen:

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

docker-rollout hangt tijdens deployment

De health check slaagt niet binnen de timeout. Controleer het interval en de retries van de health check. Verhoog de timeout:

docker rollout -t 120 app

Watchtower werkt niet meer na Docker-update

Docker Engine 29 vereist minimaal API v1.44. Watchtower gebruikt API v1.25. Migreer naar Diun voor notificaties of docker-rollout voor geautomatiseerde updates zonder downtime.

Diun detecteert geen nieuwe images

Controleer het cronschema in DIUN_WATCH_SCHEDULE. Start een handmatige scan:

docker compose exec diun diun image list

Controleer de Diun-logs op registryauthenticatiefouten:

docker compose logs diun --tail 30

Copyright 2026 Virtua.Cloud. Alle rechten voorbehouden. Deze inhoud is een origineel werk van het Virtua.Cloud-team. Reproductie, herpublicatie of herdistributie zonder schriftelijke toestemming is verboden.

Klaar om het zelf te proberen?

Deploy uw eigen server in seconden. Linux, Windows of FreeBSD.

Bekijk VPS-aanbod