← Terug naar tutorials

Production Readiness Checklist voor Dockerized Microservices (DevOps)

devopsdockermicroservicesproduction readinesskubernetesci/cdobservabilitysecurity

Production Readiness Checklist voor Dockerized Microservices (DevOps)

Deze tutorial is een praktische, productiegerichte checklist voor Dockerized microservices. Het doel is dat je services voorspelbaar deploybaar, veilig, observeerbaar, schaalbaar en herstelbaar zijn in een echte productieomgeving (Kubernetes, Docker Swarm, ECS, Nomad of “plain” Docker). De voorbeelden gebruiken vooral Docker CLI en generieke Linux-commando’s; waar relevant noem ik ook Kubernetes-equivalenten.


1) Basisprincipes: definieer “production readiness”

Een microservice is “production ready” als je:

De rest van dit document is een checklist met uitleg en concrete commando’s.


2) Container image: buildkwaliteit, minimalisme en determinisme

2.1 Gebruik multi-stage builds

Multi-stage builds verminderen image-grootte en attack surface. Voorbeeld (Go), maar conceptueel hetzelfde voor Node/Java/.NET:

docker build -t myservice:local .
docker image ls myservice:local

Controleer lagen en grootte:

docker history myservice:local
docker image inspect myservice:local --format '{{.Size}}'

Waarom dit belangrijk is: kleinere images laden sneller, hebben minder packages (minder CVE’s) en starten sneller bij autoscaling.

2.2 Pin base images op digest, niet alleen tag

Tags zoals alpine:latest zijn veranderlijk. Pin op digest:

docker pull alpine:3.20
docker image inspect alpine:3.20 --format '{{index .RepoDigests 0}}'

Gebruik vervolgens alpine@sha256:... in je Dockerfile. Dit maakt builds reproduceerbaarder en verkleint supply-chain risico’s.

2.3 Minimaliseer packages en verwijder build-tools

Installeer alleen runtime dependencies. Verwijder caches:

Controleer welke binaries aanwezig zijn:

docker run --rm myservice:local sh -lc 'ls -lah /usr/bin | head'

2.4 Maak builds reproduceerbaar

Voorbeeld: bouw met BuildKit en toon digest:

DOCKER_BUILDKIT=1 docker build -t myservice:ci .
docker image inspect myservice:ci --format '{{.Id}}'

3) Runtime security: least privilege en hardening

3.1 Draai als niet-root

In productie wil je vrijwel nooit root in de container. Controleer:

docker run --rm myservice:local id

Als je uid=0(root) ziet: fixen. In Dockerfile: maak user en switch.

Waarom: bij een exploit is de impact kleiner; bovendien blokkeren veel platforms root.

3.2 Read-only filesystem + tmpfs waar nodig

Veel services hoeven niet naar disk te schrijven. Test read-only:

docker run --rm --read-only myservice:local

Als je app schrijft naar /tmp, mount tmpfs:

docker run --rm --read-only --tmpfs /tmp:rw,noexec,nosuid,size=64m myservice:local

Waarom: beperkt persistence voor attackers en voorkomt dat containers “stiekem” stateful worden.

3.3 Capabilities minimaliseren

Standaard heeft Docker al een set capabilities; minimaliseer verder:

docker run --rm --cap-drop=ALL --cap-add=NET_BIND_SERVICE myservice:local

Alleen toevoegen wat je nodig hebt. Veel HTTP services op poort 8080 hebben zelfs NET_BIND_SERVICE niet nodig.

3.4 Seccomp/AppArmor en no-new-privileges

Controleer of no-new-privileges werkt:

docker run --rm --security-opt no-new-privileges myservice:local

In Kubernetes kun je dit via securityContext afdwingen (conceptueel; details platformafhankelijk).

3.5 Secrets: nooit in images, nooit in logs

Test dat je image geen secrets bevat:

docker run --rm myservice:local sh -lc 'grep -R "API_KEY" -n / 2>/dev/null | head'

Dit is geen waterdichte methode, maar kan grove fouten vinden.


4) Configuratiebeheer: 12-factor in praktijk

4.1 Config via environment variables

Zorg dat je service alle runtime config uit env kan halen:

Test lokaal:

docker run --rm -e PORT=8080 -e LOG_LEVEL=info myservice:local

4.2 Validatie bij startup

Een service moet bij start fail-fast als verplichte config ontbreekt. Anders krijg je “half werkende” pods.

Simuleer ontbrekende variabele:

docker run --rm myservice:local
echo $?

Verwacht: non-zero exit code en duidelijke foutmelding.

4.3 Scheid config per omgeving

Gebruik geen if env == prod spaghetti. Gebruik:


5) Networking & API: timeouts, retries, idempotency

5.1 Strikte timeouts

Zonder timeouts kunnen threads/connection pools vollopen. Controleer:

Test met een “hangende” upstream (voorbeeld met curl naar een niet-routable IP):

time curl -m 2 http://10.255.255.1:1234/ || true

Je service moet vergelijkbare grenzen afdwingen.

5.2 Retries met backoff, maar niet blind

Retries kunnen incidenten verergeren (retry storm). Gebruik:

5.3 Idempotency voor write endpoints

Voor POST/charge/order: gebruik idempotency keys. Dit voorkomt dubbele acties bij retries.


6) Health checks: liveness, readiness, startup

6.1 Liveness vs readiness

Voor Docker kun je HEALTHCHECK gebruiken en inspecteren:

docker inspect --format='{{json .State.Health}}' <container_id> | jq

Run met healthcheck en kijk status:

docker run -d --name mysvc myservice:local
docker ps
docker inspect mysvc --format '{{.State.Health.Status}}'
docker logs mysvc --tail 50
docker rm -f mysvc

Best practice: readiness endpoint moet afhankelijkheden checken (DB connectiviteit) maar niet te zwaar. Liveness vaak alleen “event loop leeft”.

6.2 Graceful shutdown en SIGTERM

Orchestrators sturen SIGTERM en wachten een grace period. Je app moet:

Test met Docker:

docker run -d --name mysvc -p 8080:8080 myservice:local
docker kill --signal=SIGTERM mysvc
docker logs mysvc --tail 100

Controleer dat je app netjes afsluit en exit code 0 geeft.


7) Logging: structuur, correlatie en geen lokale files

7.1 Log naar stdout/stderr

In containers is “log to file” meestal fout (tenzij sidecar/agent). Controleer:

docker run --rm myservice:local 2>&1 | head -n 50

7.2 Structured logging (JSON) + correlation IDs

Gebruik JSON logs met velden:

Test dat logs parsebaar zijn:

docker run --rm myservice:local 2>&1 | head -n 5 | jq .

7.3 Geen secrets in logs

Zoek op patronen:

docker run --rm myservice:local 2>&1 | grep -iE 'password|secret|token' | head

Beter: implementeer log redaction.


8) Metrics & tracing: SLO’s en diagnose

8.1 Golden signals

Meet minimaal:

Expose metrics endpoint (bijv. /metrics voor Prometheus). Test:

docker run -d --name mysvc -p 8080:8080 myservice:local
curl -fsS http://localhost:8080/metrics | head -n 30
docker rm -f mysvc

8.2 Distributed tracing (OpenTelemetry)

Zorg dat je trace context doorgeeft via headers (W3C Trace Context). In productie kun je sampling instellen.

Verifieer dat je service headers accepteert en doorstuurt:

curl -v -H 'traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01' \
  http://localhost:8080/health || true

9) Resource management: CPU, memory, ulimits, GC

9.1 Stel limieten in

Zonder limieten kan één service de node uitputten. In Docker:

docker run --rm --memory=512m --cpus=1.0 myservice:local

Controleer runtime usage:

docker stats --no-stream

Waarom: voorspelbaarheid en eerlijke scheduling. In Kubernetes zijn requests/limits cruciaal voor autoscaling en stabiliteit.

9.2 OOM gedrag en memory leaks

Test onder load (voorbeeld met hey):

hey -z 30s -c 50 http://localhost:8080/

Kijk naar memory groei:

docker stats mysvc

Als memory blijft stijgen: leak of caching zonder bounds.

9.3 Ulimits en file descriptors

High-throughput services raken snel door file descriptors heen.

Check huidige limieten:

docker run --rm myservice:local sh -lc 'ulimit -n; ulimit -a'

Run met hogere nofile (indien nodig):

docker run --rm --ulimit nofile=65535:65535 myservice:local

10) Data & state: databases, migrations, backward compatibility

10.1 Schema migrations: gecontroleerd en herhaalbaar

Migrations moeten:

Voorbeeld: migration container run (conceptueel):

docker run --rm \
  -e DATABASE_URL="$DATABASE_URL" \
  myservice:local ./migrate up

10.2 Connection pooling en limits

Zorg dat je pool niet te groot is per replica. Anders overspoel je DB.

Test connectiviteit:

docker run --rm -e DATABASE_URL="$DATABASE_URL" myservice:local ./healthcheck-db

11) CI/CD: build, test, scan, sign, promote

11.1 Automatische tests in pipeline

Minimaal:

Run tests in container:

docker build -t myservice:test --target test .
docker run --rm myservice:test

11.2 Vulnerability scanning (image + dependencies)

Gebruik een scanner zoals Trivy (voorbeeld):

trivy image myservice:ci
trivy fs --scanners vuln,secret .

Interpretatie: focus op exploiteerbare CVE’s in runtime layers. Fix door base image updates en dependency bumps.

11.3 SBOM genereren

SBOM (Software Bill of Materials) helpt bij compliance en incident response.

Met Syft:

syft myservice:ci -o spdx-json > sbom.spdx.json

11.4 Image signing en provenance

Supply-chain hardening: sign images (cosign). Voorbeeld:

cosign sign --key cosign.key myregistry.example.com/myservice@sha256:<digest>
cosign verify --key cosign.pub myregistry.example.com/myservice@sha256:<digest>

Gebruik digests in deploy manifests zodat je exact weet wat draait.

11.5 Promote via registries, niet rebuilden

Build één keer, promote dezelfde digest van staging naar prod. Dit voorkomt “works in staging, fails in prod” door rebuild drift.


12) Deployment strategie: rolling, blue/green, canary

12.1 Rolling updates met readiness gates

Zonder readiness kan verkeer naar half-opgestarte pods. Zorg dat:

12.2 Canary releases

Stuur een klein percentage verkeer naar nieuwe versie. Meet golden signals en rollback bij regressie.

Praktisch: kanary via ingress/service mesh of load balancer weights.

12.3 Feature flags

Gebruik feature flags voor risicovolle veranderingen. Zo kun je zonder redeploy uitschakelen.


13) Incident response: debugbaarheid en forensics

13.1 Debug tooling: minimalisme vs noodzaak

Minimal images missen tools (curl, dig). In productie wil je vaak:

Voor Docker kun je in dezelfde network namespace debuggen met een toolbox container:

docker run -d --name mysvc -p 8080:8080 myservice:local
docker run --rm --network container:mysvc nicolaka/netshoot curl -v http://localhost:8080/health
docker rm -f mysvc

13.2 Core dumps en crash diagnostics

Voor native apps: configureer crash dumps veilig (let op privacy). Zorg dat je crash info niet in logs lekt.

13.3 Runbooks en on-call

Minimaal:


14) Performance: cold start, caching, load tests

14.1 Cold start meten

Meet starttijd tot readiness:

start=$(date +%s)
cid=$(docker run -d -p 8080:8080 myservice:local)
until curl -fsS http://localhost:8080/ready >/dev/null; do sleep 0.2; done
end=$(date +%s)
echo "Ready in $((end-start))s"
docker rm -f "$cid"

14.2 Load en stress tests

Gebruik hey, wrk of k6. Voorbeeld:

hey -n 20000 -c 200 http://localhost:8080/

Kijk naar p95/p99 en error rates. Combineer met docker stats voor saturatie.

14.3 Caching met bounds

Caches moeten:

Anders krijg je memory pressure en OOM kills.


15) Betrouwbaarheid: retries, rate limiting, bulkheads

15.1 Rate limiting

Bescherm jezelf en downstream dependencies. Implementeer:

Test met snelle requests:

for i in $(seq 1 200); do curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8080/; done | sort | uniq -c

Je wilt bij overload gecontroleerde 429’s zien in plaats van 500’s.

15.2 Bulkheads

Scheid thread pools/connection pools per dependency zodat één trage dependency niet alles blokkeert.


16) Backups, restore en disaster recovery

16.1 Backups zijn waardeloos zonder restore tests

Maak een restore drill:

Voorbeeld (Postgres dump/restore conceptueel):

pg_dump "$DATABASE_URL" -Fc -f backup.dump
createdb restore_test
pg_restore -d restore_test backup.dump

16.2 Multi-region / failover

Als je business dit vereist: ontwerp voor failover. Denk aan:


17) Compliance en privacy

17.1 Data classificatie

Weet welke data je verwerkt (PII/PCI/PHI). Log geen PII. Masker identifiers waar nodig.

17.2 Audit logging

Voor security-relevante acties (admin changes, permission updates) aparte audit logs met:


18) Concrete checklist (samenvatting)

Gebruik dit als “go/no-go” voor productie.

Image & build

Security

Config & lifecycle

Observability

Performance & reliability

Data & deploy


19) Snelle “pre-flight” commando’s (praktisch)

Een compacte set checks die je vaak direct kunt draaien:

# 1) Image info
docker image ls myservice:ci
docker image inspect myservice:ci --format 'ID={{.Id}} Size={{.Size}} Created={{.Created}}'

# 2) Run as non-root?
docker run --rm myservice:ci id

# 3) Health endpoints (voorbeeld)
cid=$(docker run -d -p 8080:8080 --name mysvc myservice:ci)
curl -fsS http://localhost:8080/health
curl -fsS http://localhost:8080/ready
docker logs mysvc --tail 50

# 4) Graceful shutdown
docker kill --signal=SIGTERM mysvc
sleep 2
docker logs mysvc --tail 100
docker rm -f mysvc >/dev/null 2>&1 || true

# 5) CVE scan (Trivy)
trivy image myservice:ci

Pas endpoints en poorten aan naar jouw service.


20) Afronding: hoe je deze checklist gebruikt

Als je wilt, kan ik deze checklist omzetten naar een concrete CI pipeline-sectie (bijv. GitHub Actions/GitLab CI) met exact dezelfde stappen (build → test → trivy → syft → cosign → push) en een bijbehorend release-proces op basis van image digests.