Docker-updatestrategie: containerupdates zonder downtime op een VPS
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.
CMDvoert het direct uit. GebruikCMD-SHELLals 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
unhealthymarkeert. - 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/1
✔ Container 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.
- 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
- Bewerk je compose-bestand om de vorige versie te pinnen:
services:
app:
image: myapp:2.4.1
- Voer de rollback uit:
docker compose up -d app
[+] Running 1/1
✔ Container app-1 Started 0.7s
- 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
truebewaakt Diun alle draaiende containers. Zet opfalseen 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:
- Is 2-10 seconden downtime acceptabel? Gebruik
docker compose pull && docker compose up -d. - Wil je over updates weten vóór je ze toepast? Voeg Diun toe.
- Is zero downtime vereist? Heb je een reverse proxy? Gebruik docker-rollout.
- 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