Docker Compose resourcelimieten, healthchecks en herstartbeleid

11 min leestijd·Matthieu·vpsproductioncontainersdocker-composedocker|

Je Compose-bestand werkt in development maar is niet klaar voor productie. Leer hoe je geheugen-/CPU-limieten, healthchecks, herstartbeleid en opstartvolgorde toevoegt om je VPS te beschermen tegen OOM-kills en cascade-storingen.

Een Compose-bestand dat docker compose up foutloos uitvoert is niet productie-klaar. Zonder resourcelimieten kan één container al het hostgeheugen opslokken en de Linux OOM-killer activeren, waardoor elke service op de VPS plat gaat. Zonder healthchecks heeft Docker geen manier om een vastgelopen proces te detecteren. Zonder herstartbeleid blijft een gecrashte container dood tot je het zelf opmerkt.

Deze drie systemen werken samen: resourcelimieten voorkomen ongecontroleerd verbruik, healthchecks detecteren storingen en herstartbeleid zorgt voor herstel. Deze gids behandelt alle drie als geïntegreerde productie-hardening laag voor Docker Compose v2.

Vereisten: Een werkende Docker Compose-installatie op een VPS. Bekendheid met de basisstructuur van een Compose-bestand. Voor een opfrisser, zie [-> docker-compose-multi-service-vps].

Hoe stel je geheugen- en CPU-limieten in met Docker Compose?

Gebruik de deploy.resources.limits sleutel in je servicedefinitie. Stel memory in op een waarde als 512M of 1G voor een hard plafond. Stel cpus in op een decimale string als '0.5' voor een halve core. Het containerproces wordt door de OOM-killer beëindigd als het de geheugenlimiet overschrijdt. CPU-limieten vertragen het proces in plaats van het te beëindigen.

services:
  api:
    image: node:22-slim
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 256M

Wat dit doet: De api-container kan maximaal 1 CPU-core en 512 MB RAM gebruiken. Docker garandeert minstens 0,25 core en 256 MB beschikbaar, zelfs als andere containers om resources concurreren.

Controleer of de limieten zijn toegepast:

docker compose up -d
docker stats --no-stream --format "table {{.Name}}\t{{.MemUsage}}\t{{.CPUPerc}}"

De kolom MEM USAGE / LIMIT toont zowel het huidige gebruik als het geconfigureerde plafond. Je zou 512MiB als limiet moeten zien, niet het totale host-RAM. Als je het totale hostgeheugen ziet, zijn de limieten niet actief.

Wat is het verschil tussen limits en reservations?

Limits zijn harde plafonds. De container kan ze niet overschrijden. Reservations zijn zachte garanties. Docker gebruikt ze voor planningsbeslissingen en geheugendrukverwerking.

Instelling Functie Wanneer relevant
limits.memory Hard plafond. OOM-kill bij overschrijding. Altijd. Voorkomt op hol geslagen containers.
limits.cpus Beperking. Proces vertraagt. CPU-intensieve workloads (builds, inference).
reservations.memory Gegarandeerd minimum. Host onder geheugendruk.
reservations.cpus Gegarandeerd CPU-aandeel. Meerdere CPU-intensieve containers.

Zonder reservations wijst Docker resources toe op basis van wie het eerst komt. Onder druk kan elke container tekort komen. Stel reservations in op het minimum dat je service nodig heeft om te functioneren.

Wat gebeurt er als een Docker-container zijn geheugenlimiet overschrijdt?

De Linux kernel OOM-killer beëindigt het hoofdproces van de container met SIGKILL. Docker registreert exitcode 137 (128 + 9, waarbij 9 SIGKILL is). Als het herstartbeleid on-failure of always is, herstart Docker de container automatisch.

Controleer of een container door OOM-kill is beëindigd:

docker inspect api-1 --format '{{.State.OOMKilled}}'

Output: true bevestigt een OOM-kill.

Voor meer detail:

docker inspect api-1 --format '{{json .State}}' | python3 -m json.tool

Zoek naar "OOMKilled": true, "ExitCode": 137 en "RestartCount" om te zien hoeveel keer de container is herstart.

Zonder limieten blijft de container geheugen toewijzen tot de host leeg is. De kernel beëindigt dan systeembreed processen om RAM vrij te maken. Dit kan je database, reverse proxy of SSH-daemon meenemen. Limieten beperken de schade tot de problematische container.

Hoe plan je het resourcebudget voor containers op een VPS?

Op een VPS met vaste resources moet je geheugen verdelen over alle containers. Laat ruimte over voor het host-besturingssysteem en Docker zelf.

Voorbeeldbudget voor een VPS met 8 GB:

Component Geheugen
Host OS + Docker engine 1 GB
PostgreSQL 2 GB
Redis 512 MB
Node.js API 1 GB
Nginx 128 MB
Achtergrondwerker 1 GB
Marge (niet toegewezen) 2,35 GB

Houd 20-30% niet-toegewezen als marge. Als de som van je containerlimieten meer is dan het totale host-RAM, riskeer je dat de host-OOM-killer ingrijpt, die Docker's containergrenzen negeert.

Controleer de totale toewijzing tegen het hostgeheugen:

free -h
docker stats --no-stream --format "{{.Name}}: {{.MemUsage}}"

Hoe configureer je een healthcheck in Docker Compose?

Voeg een healthcheck-blok toe aan je servicedefinitie. Docker voert het testcommando uit op het opgegeven interval en markeert de container als unhealthy na het geconfigureerde aantal opeenvolgende mislukkingen. Andere services kunnen afhangen van deze gezondheidsstatus voor de opstartvolgorde.

services:
  api:
    image: node:22-slim
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s
      start_interval: 2s

Healthcheck-parameters uitgelegd:

Parameter Standaard Aanbevolen Wat het regelt
interval 30s 15-60s Tijd tussen checks na opstarten
timeout 30s 5-10s Maximale duur van een check
retries 3 3-5 Mislukkingen voor unhealthy
start_period 0s 10-60s Gratieperiode voor traag startende services
start_interval 5s 2-5s Interval tijdens opstarten (Compose v2.20+)

De start_interval parameter (toegevoegd in Compose v2.20) maakt frequentere checks mogelijk tijdens het opstarten. De container gaat van starting naar healthy zodra de eerste check slaagt tijdens de start_period. Daarna draaien checks op het normale interval.

Wat is het verschil tussen CMD en CMD-SHELL in healthchecks?

CMD voert het commando direct uit zonder shell. CMD-SHELL stuurt het door /bin/sh -c. Gebruik CMD waar mogelijk. Het vermijdt shell-overhead en elimineert PID 1-problemen waarbij de shell, niet je check-commando, signalen ontvangt.

# CMD-formaat - geen shell, voert de binary direct uit
healthcheck:
  test: ["CMD", "pg_isready", "-U", "postgres"]

# CMD-SHELL-formaat - via /bin/sh -c
healthcheck:
  test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]

# String-snelkoppeling - equivalent aan CMD-SHELL
healthcheck:
  test: curl -f http://localhost:3000/health || exit 1

Gebruik CMD-SHELL wanneer je shell-functies nodig hebt zoals ||, pipes of variabele-expansie. Gebruik CMD voor eenvoudige binary-uitvoering.

Welk healthcheck-commando gebruik je voor PostgreSQL, Redis en Nginx?

Elke service heeft een healthcheck nodig die de mogelijkheid van de service test om verzoeken af te handelen, niet alleen of het proces draait.

Service Healthcheck-commando Wat het test
PostgreSQL ["CMD", "pg_isready", "-U", "postgres"] Accepteert verbindingen
Redis ["CMD", "redis-cli", "ping"] Reageert op commando's
Nginx `["CMD-SHELL", "curl -f http://localhost/
Node.js-app `["CMD-SHELL", "curl -f http://localhost:3000/health
MySQL/MariaDB ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] Engine gereed, niet alleen socket open

Belangrijk: Voor curl-gebaseerde healthchecks moet de image curl bevatten. Veel slim-images hebben het niet. Installeer het in je Dockerfile, gebruik wget als alternatief, of schrijf een minimaal health-endpoint dat je applicatieframework standaard biedt.

Controleer de healthcheck-status:

docker compose ps

Zoek naar (healthy) of (unhealthy) in de STATUS-kolom. Voor gedetailleerde healthcheck-geschiedenis:

docker inspect api-1 --format '{{json .State.Health}}' | python3 -m json.tool

Dit toont de laatste checkresultaten, inclusief stdout/stderr-output van mislukte checks. Let goed op: als FailingStreak blijft oplopen, is je check-commando fout of heeft de service een echt probleem.

Hoe werken herstartbeleiden samen met healthchecks?

Herstartbeleid bepaalt wat Docker doet als een container stopt. Healthchecks bepalen hoe Docker problemen detecteert in draaiende containers. Samen vormen ze een automatische herstelsysteem: de healthcheck detecteert de storing, de container wordt gestopt en het herstartbeleid brengt hem terug.

services:
  api:
    restart: on-failure:5
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s

Herstartbeleiden vergeleken

Beleid Bij crash Bij herstart Bij docker stop Geschikt voor
no Blijft dood Blijft dood Blijft dood Eenmalige taken, debugging
always Herstart Herstart Herstart Kerninfrastructuur (databases, proxy's)
unless-stopped Herstart Herstart Blijft dood De meeste productieservices
on-failure:N Herstart (max N keer) Blijft dood Blijft dood Services die niet eindeloos moeten herstarten

on-failure:5 betekent dat Docker tot 5 keer herstart. Daarna blijft de container dood. Dit voorkomt herstartlussen die CPU verspillen aan een fundamenteel kapotte service.

De OOM + herstart-interactie: Wanneer een container zijn geheugenlimiet bereikt en door OOM-kill wordt beëindigd (exitcode 137), behandelt Docker dit als een mislukking. Met on-failure:5 herstart het tot 5 keer. Als de service geheugen lekt, wordt deze herhaaldelijk beëindigd en herstart tot de herstartlimiet is bereikt. Controleer de herstartteller:

docker inspect api-1 --format '{{.RestartCount}}'

Voor de meeste productieservices, gebruik unless-stopped. Het herstart bij crashes en host-herstarts, maar respecteert handmatige docker compose stop-commando's. Gebruik on-failure:N voor services waarbij een crashlus een alarm moet triggeren, niet stilletjes eindeloos opnieuw proberen.

Hoe laat je een service wachten tot een andere service gezond is?

Gebruik depends_on met condition: service_healthy. Dit laat Docker Compose wachten tot de healthcheck van de afhankelijkheid slaagt voordat de afhankelijke service wordt gestart.

services:
  db:
    image: postgres:17
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "postgres"]
      interval: 5s
      timeout: 3s
      retries: 5
      start_period: 10s
    restart: unless-stopped

  api:
    image: myapp:latest
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 3
    restart: unless-stopped

Zonder condition: service_healthy wacht depends_on alleen tot de container start, niet tot de service erin klaar is. PostgreSQL heeft meerdere seconden nodig om te initialiseren. Je applicatie zou crashen bij het verbinden met een database die nog geen verbindingen accepteert.

De restart: true optie binnen depends_on (Compose v2.21+) vertelt Docker om de afhankelijke service te herstarten als de afhankelijkheid herstart:

depends_on:
  db:
    condition: service_healthy
    restart: true

Dit is handig wanneer je applicatie de databaseverbinding cachet en niet kan herstellen van een database-herstart zonder zelf volledig te herstarten.

Welke ulimits stel je in voor productiecontainers?

Stel nofile (open bestandsdescriptors) en nproc (maximaal aantal processen) in voor services die veel gelijktijdige verbindingen afhandelen. Elke TCP-verbinding, geopend bestand en pipe verbruikt een bestandsdescriptor. De standaardlimiet (1024 in veel images) is te laag voor databases en services met veel verkeer.

services:
  db:
    image: postgres:17
    ulimits:
      nofile:
        soft: 65536
        hard: 65536
      nproc:
        soft: 4096
        hard: 4096

Controleer binnen de container:

docker compose exec db cat /proc/1/limits

Zoek naar Max open files en Max processes. De waarden moeten overeenkomen met wat je hebt ingesteld.

Forkbomb-preventie met PID-limiet

Stel deploy.resources.limits.pids in om het aantal processen te beperken dat een container kan aanmaken. Dit voorkomt dat forkbombs en ongecontroleerde procesaanmaak alle PID's op de host opgebruiken.

services:
  api:
    image: myapp:latest
    deploy:
      resources:
        limits:
          pids: 200

Als je deploy.resources niet gebruikt voor CPU-/geheugenlimieten, werkt de top-level pids_limit sleutel ook. Maar wanneer deploy.resources.limits aanwezig is, moet je de PID-limiet daar ook plaatsen. Het mixen van beide veroorzaakt een validatiefout in Compose v5+.

200 PID's is ruim voor een typische webapplicatie. Een Node.js-app gebruikt er ongeveer 10-30. PostgreSQL gebruikt ruwweg één proces per verbinding plus achtergrondwerkers. Dimensioneer op 2-3x je verwachte piek.

Hoe beperk je de loggrootte van containers?

Zonder loglimieten groeien containerlogs onbeperkt. Een spraakzame service kan je schijf in uren vullen. Stel max-size en max-file in op de json-file logging-driver om logs automatisch te roteren.

services:
  api:
    image: myapp:latest
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

Dit bewaart maximaal 3 bestanden van 10 MB elk, waardoor logopslag beperkt wordt tot 30 MB per service. Pas aan op basis van je debug-behoeften. Gebruik een YAML-anker om dezelfde logconfiguratie op alle services toe te passen:

x-logging: &default-logging
  driver: "json-file"
  options:
    max-size: "10m"
    max-file: "3"

services:
  api:
    image: myapp:latest
    logging: *default-logging
  worker:
    image: myapp:latest
    logging: *default-logging

stop_grace_period instellen voor schoon afsluiten

Wanneer Docker een container stopt, stuurt het SIGTERM en wacht tot het proces netjes afsluit. Als het proces niet binnen de gratieperiode stopt, stuurt Docker SIGKILL. De standaardwaarde is 10 seconden.

services:
  db:
    image: postgres:17
    stop_grace_period: 30s

  api:
    image: myapp:latest
    stop_grace_period: 5s

Databases hebben langere gratieperiodes nodig om schrijfacties weg te schrijven en verbindingen netjes te sluiten. Webservers en API-processen sluiten doorgaans af binnen een paar seconden. Stel de gratieperiode in op basis van de werkelijke afsluitduur van je service, met een kleine buffer.

Volledig productie-klaar Compose-bestand

Dit voorbeeld combineert alle instellingen voor een typische webapplicatie-stack: Nginx reverse proxy, Node.js API, PostgreSQL database en Redis cache.

x-logging: &default-logging
  driver: "json-file"
  options:
    max-size: "10m"
    max-file: "3"

services:
  nginx:
    image: nginx:1.27-alpine
    ports:
      - "80:80"
      - "443:443"
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 128M
        reservations:
          memory: 64M
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost/ || exit 1"]
      interval: 30s
      timeout: 5s
      retries: 3
    restart: unless-stopped
    stop_grace_period: 5s
    logging: *default-logging
    depends_on:
      api:
        condition: service_healthy

  api:
    image: myapp:latest
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 512M
          pids: 200
        reservations:
          cpus: '0.25'
          memory: 256M
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 15s
      start_interval: 2s
    restart: unless-stopped
    stop_grace_period: 5s
    logging: *default-logging
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy

  db:
    image: postgres:17
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 2G
        reservations:
          cpus: '0.5'
          memory: 1G
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "postgres"]
      interval: 10s
      timeout: 3s
      retries: 5
      start_period: 15s
    restart: unless-stopped
    stop_grace_period: 30s
    ulimits:
      nofile:
        soft: 65536
        hard: 65536
    logging: *default-logging
    volumes:
      - pgdata:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
        reservations:
          memory: 128M
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 3
    restart: unless-stopped
    stop_grace_period: 5s
    logging: *default-logging

volumes:
  pgdata:

secrets:
  db_password:
    file: ./secrets/db_password.txt

Let op: Het databasewachtwoord gebruikt Docker secrets met POSTGRES_PASSWORD_FILE in plaats van een POSTGRES_PASSWORD omgevingsvariabele in platte tekst. Maak het secrets-bestand aan met beperkte rechten:

mkdir -p secrets
openssl rand -base64 32 > secrets/db_password.txt
chmod 600 secrets/db_password.txt

Controlelijst

Na het toepassen van alle instellingen, loop je deze controles door om te bevestigen dat alles actief is.

1. Controleer of resourcelimieten zijn toegepast:

docker stats --no-stream --format "table {{.Name}}\t{{.MemUsage}}\t{{.CPUPerc}}"

De kolom MEM USAGE / LIMIT toont zowel het huidige gebruik als het geconfigureerde plafond. Elke container moet zijn geconfigureerde geheugenlimiet tonen, niet het totale RAM van de host.

2. Controleer healthcheck-status:

docker compose ps

Alle services moeten (healthy) tonen in de STATUS-kolom. Als er (health: starting) staan, wacht tot de start_period is verstreken.

3. Controleer herstartbeleid:

docker inspect --format '{{.HostConfig.RestartPolicy.Name}}:{{.HostConfig.RestartPolicy.MaximumRetryCount}}' $(docker compose ps -q)

4. Controleer ulimits binnen een container:

docker compose exec db cat /proc/1/limits | grep -E "open files|processes"

5. Controleer logconfiguratie:

docker inspect --format '{{.HostConfig.LogConfig.Type}} max-size={{index .HostConfig.LogConfig.Config "max-size"}} max-file={{index .HostConfig.LogConfig.Config "max-file"}}' $(docker compose ps -q api)

6. Test de volledige herstelketen:

Stop een container en observeer het herstel:

docker compose stop api
docker compose ps  # api zou Exited moeten tonen
docker compose start api
docker compose ps  # api zou (healthy) moeten tonen na de start_period
docker inspect $(docker compose ps -q api) --format 'RestartCount: {{.RestartCount}}'

Om automatisch herstarten bij een echte crash te testen, verlaag je de geheugenlimiet onder het minimum van de applicatie. Het herstartbeleid treedt in werking wanneer het proces zelf stopt. Merk op dat docker kill in recente Docker-versies geen herstartbeleid activeert.

Snelreferentie voor resourcedimensionering

Startpunten voor veelvoorkomende services op een VPS. Pas aan op basis van je werkelijke belasting.

Service Geheugenlimiet CPU-limiet Healthcheck
PostgreSQL 1-4 GB 1.0-2.0 pg_isready -U postgres
Redis 256M-1G 0.25-0.5 redis-cli ping
Node.js API 256M-1G 0.5-1.0 curl -f http://localhost:PORT/health
Nginx 64M-256M 0.25-0.5 curl -f http://localhost/
Ollama (LLM) 4-8 GB 2.0-4.0 curl -f http://localhost:11434/
Achtergrondwerker 256M-1G 0.5-1.0 Applicatiespecifieke check

Gaat er iets mis?

Container blijft herstarten (herstartlus):

docker compose logs api --tail 50
docker inspect api-1 --format '{{.State.ExitCode}} OOM:{{.State.OOMKilled}} Restarts:{{.RestartCount}}'

Als OOMKilled: true, verhoog de geheugenlimiet. Als de exitcode niet 137 is, controleer de applicatielogs voor de werkelijke fout.

Healthcheck faalt altijd:

docker inspect api-1 --format '{{json .State.Health.Log}}' | python3 -m json.tool

Dit toont de output van elke check. Veelvoorkomende oorzaken: het health-endpoint bestaat niet, curl is niet geïnstalleerd in de image, of de service luistert op een andere poort dan waar de check naar wijst.

depends_on wacht niet:

Zorg ervoor dat de afhankelijkheid een healthcheck heeft gedefinieerd. Zonder heeft condition: service_healthy niets om op te wachten en geeft Compose een fout bij het opstarten.

Limieten worden niet getoond in docker stats:

Controleer of je Docker Compose v2 gebruikt (de docker compose plugin, niet het oude docker-compose binary). Controleer je versie:

docker compose version

De deploy.resources sleutel vereist Compose v2. Als je een oudere versie gebruikt, zie [-> docker-commands] voor installatie-instructies.

Logs lezen wanneer iets faalt:

journalctl -u docker -f
docker compose logs -f --tail 100

Het Docker daemon-log toont OOM-events en containerlevenscyclusveranderingen. De Compose-logs tonen applicatie-output.


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