Production concerns: resources, health and logging

CPU and memory limits, healthchecks that mean something, log rotation, and shutting down without dropping requests.

Limits and reservations

docker run -d --name api \
  --memory 512m --memory-reservation 256m --memory-swap 512m \
  --cpus 1.5 --cpu-shares 512 \
  --pids-limit 300 \
  --blkio-weight 500 \
  api:1.4.2

docker stats --no-stream api
docker inspect -f '{{.HostConfig.Memory}} {{.HostConfig.NanoCpus}}' api

# Compose equivalent
# services:
#   api:
#     mem_limit: 512m
#     cpus: 1.5
#     pids_limit: 300
FlagMeaning when hit
--memoryThe container is OOM-killed; exit code 137
--memory-reservationSoft target used under host memory pressure
--cpusThrottled with the CFS quota; latency rises, nothing dies
--cpu-sharesRelative weight only, applied when the host is contended
--pids-limitFork fails with resource temporarily unavailable
--memory-swapEqual to --memory means no swap; unset means twice the limit
⚠️
A runtime that reads total host memory rather than the cgroup limit will size its heap for the whole machine and then be killed. Set the application's own ceiling as well — for example NODE_OPTIONS=--max-old-space-size=384 or a JVM -XX:MaxRAMPercentage — and check docker inspect --format='{{.State.OOMKilled}}' before blaming the code.

Healthchecks

HEALTHCHECK --interval=30s --timeout=3s --start-period=20s --retries=3 \
  CMD node -e "require('http').get('http://127.0.0.1:3000/health', r => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))"
services:
  api:
    build: .
    healthcheck:
      test: ["CMD", "curl", "-fsS", "http://localhost:3000/health"]
      interval: 30s
      timeout: 3s
      retries: 3
      start_period: 20s
  worker:
    depends_on:
      api:
        condition: service_healthy
  • The endpoint must prove the dependencies too — a check that returns 200 while the database is unreachable is worse than no check, because it lies to the scheduler.
  • start-period is the grace window for a slow boot; without it a healthy container is marked unhealthy while it warms up.
  • docker ps shows healthy or unhealthy, and an unhealthy container is not restarted automatically — the orchestrator decides that, so make sure something does.
  • Keep the check cheap: a heavy query every 30 seconds is load you added yourself.

Logging and stopping cleanly

Log driverWhere logs goNote
json-fileFiles under /var/lib/dockerDefault; needs max-size or it fills the disk
journaldThe systemd journaljournalctl CONTAINER_NAME=api
syslog / fluentd / gelfAn external collectorContainer disappears and the logs do not
localCompressed files on the hostBetter default than json-file for high volume
noneNowhereFor noisy containers you handle yourself
docker run -d --log-driver json-file \
  --log-opt max-size=10m --log-opt max-file=5 --name api api:1.4.2

# in /etc/docker/daemon.json this becomes the default for every container
{
  "log-driver": "json-file",
  "log-opts": { "max-size": "10m", "max-file": "5" }
}

# graceful shutdown: do not swallow SIGTERM
docker run -d --init --stop-signal SIGTERM --stop-timeout 25 api:1.4.2

# in Compose
#   stop_signal: SIGTERM
#   stop_grace_period: 25s
#   logging: { driver: json-file, options: { max-size: "10m", max-file: "5" } }
  • PID 1 has no default signal handling, so a shell wrapper that does not exec your process swallows SIGTERM and the container is killed after the timeout — use an exec form or --init.
  • Flush in-flight work during the grace period, then exit 0. Dropping requests on every deploy is a self-inflicted outage.
  • Twelve-factor configuration: everything that differs between environments comes from the environment, so the same image runs everywhere.
  • Never write logs only to a file inside the container — that file dies with the container and ignores every rotation setting above.

FAQ

Container exits with 137 — out of memory or killed?
Check both: docker inspect -f '{{.State.OOMKilled}}' distinguishes an OOM kill from an external SIGKILL, and exit 143 means a SIGTERM arrived. If the OOM flag is false, look for a user or supervisor that killed it.
Do I need a healthcheck if Kubernetes manages the container?
Kubernetes uses its own probes and ignores Docker's HEALTHCHECK. Keep a proper health endpoint in the application and configure both, so the same image behaves correctly under Compose and in a cluster.

Container lifecycle, logs and debugging Data persistence and backup patterns

Last refreshed 2026-09-18.