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| Flag | Meaning when hit |
|---|---|
--memory | The container is OOM-killed; exit code 137 |
--memory-reservation | Soft target used under host memory pressure |
--cpus | Throttled with the CFS quota; latency rises, nothing dies |
--cpu-shares | Relative weight only, applied when the host is contended |
--pids-limit | Fork fails with resource temporarily unavailable |
--memory-swap | Equal 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-periodis the grace window for a slow boot; without it a healthy container is marked unhealthy while it warms up.docker psshowshealthyorunhealthy, 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 driver | Where logs go | Note |
|---|---|---|
json-file | Files under /var/lib/docker | Default; needs max-size or it fills the disk |
journald | The systemd journal | journalctl CONTAINER_NAME=api |
syslog / fluentd / gelf | An external collector | Container disappears and the logs do not |
local | Compressed files on the host | Better default than json-file for high volume |
none | Nowhere | For 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
execyour process swallows SIGTERM and the container is killed after the timeout — use anexecform 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.Related
Container lifecycle, logs and debugging Data persistence and backup patterns
Last refreshed 2026-09-18.