Skip to content
Laviyx Learn

Try

Docker and Kubernetes course menu

Docker Health Checks and Resource Limits

Add HEALTHCHECK to images, read health status, limit memory and CPU for containers, see what happens when a container runs out of memory, and manage log size.

Intermediate6 min readUpdated
On this page

Running in production means more than "it started". You need to know that a container is really working, and to stop one container from harming the rest of the server.

Health checks#

A container can be running while the app inside is stuck. A health check is a command that Docker runs regularly. Exit code 0 means healthy:

Terminal
mkdir hc
cd hc
cat > Dockerfile <<'EOF'
FROM nginx:1.30-alpine
HEALTHCHECK --interval=2s --timeout=2s --retries=2 \
  CMD wget -q --spider http://127.0.0.1/ || exit 1
EOF
docker build -q -t hc-demo . > /dev/null
docker run -d --name web hc-demo > /dev/null
docker inspect --format '{{.State.Health.Status}}' web
sleep 5
docker inspect --format '{{.State.Health.Status}}' web
Output
starting
healthy

It starts as starting, then becomes healthy. Now break it, by deleting the home page, so that the check fails:

Terminal
docker exec web sh -c 'rm /usr/share/nginx/html/index.html'
sleep 6
docker inspect --format '{{.State.Health.Status}}' web
docker ps --format '{{.Names}} {{.Status}}' | sed -E 's/Up [^(]*//'
docker rm -f web > /dev/null
Output
unhealthy
web (unhealthy)

Plain Docker only reports unhealthy; it does not restart the container. Compose can use the status with depends_on, and Kubernetes has probes that restart containers.

Make sure the tool you use exists in the image. Slim images may have no curl, and for a Python image you can use a one-line Python command.

Limit memory#

A container with no limit can use all the host's memory, and harm everything else:

Terminal
docker run -d --name small --memory=50m nginx:1.30-alpine > /dev/null
docker inspect --format '{{.HostConfig.Memory}}' small
docker stats --no-stream --format '{{.Name}} limit {{.MemUsage}}' small | sed 's/.*\/ /limit /'
docker rm -f small > /dev/null
Output
52428800
limit 50MiB

If a process uses more than the limit, the kernel kills it:

Terminal
docker run --name hog --memory=20m --memory-swap=20m alpine:3.24 sh -c 'x=a; while true; do x="$x$x"; done' 2>&1 | tail -n 1 | cut -c1-40
docker inspect --format 'Exit code {{.State.ExitCode}}, OOMKilled {{.State.OOMKilled}}' hog
docker rm hog > /dev/null
Output
Exit code 137, OOMKilled true

Exit code 137 means that the process was killed with SIGKILL (128 + 9), usually because it ran out of memory.

Limit CPU#

--cpus limits how much CPU time a container may use:

Terminal
docker run -d --name busy --cpus=0.5 alpine:3.24 sh -c 'while true; do :; done' > /dev/null
sleep 2
docker inspect --format '{{.HostConfig.NanoCpus}}' busy
docker rm -f busy > /dev/null
Output
500000000

500000000 nano-CPUs is half a core.

Limits in Compose#

YAML
services:
  api:
    image: my-api:1.0
    mem_limit: 512m
    cpus: 1.0
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 20s

start_period gives a slow app time to start before failures count.

Logs can fill a disk#

Docker keeps container logs in a JSON file, and by default it grows with no limit. Set a limit for each container:

Terminal
docker run -d --name logged --log-opt max-size=10m --log-opt max-file=3 alpine:3.24 sleep 5 > /dev/null
docker inspect --format '{{.HostConfig.LogConfig.Config}}' logged
docker rm -f logged > /dev/null
Output
map[max-file:3 max-size:10m]

To set this for all containers, add it to /etc/docker/daemon.json:

Text
{ "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } }

A production checklist#

  • A specific image tag.
  • A non-root user.
  • A health check, and a /health endpoint.
  • Memory and CPU limits.
  • A restart policy.
  • Log limits.
  • Clean handling of SIGTERM.

Common mistakes#

Practice#

Practice exercise

Run alpine:3.24 with a health check given on the command line (--health-cmd "test -f /tmp/ok", --health-interval=1s, --health-retries=1), and the command sleep 20. Print the status, create /tmp/ok, and print it again.

Show solution
Terminal
docker run -d --name chk --health-cmd "test -f /tmp/ok" --health-interval=1s --health-retries=1 alpine:3.24 sleep 20 > /dev/null
sleep 3
docker inspect --format '{{.State.Health.Status}}' chk
docker exec chk touch /tmp/ok
sleep 3
docker inspect --format '{{.State.Health.Status}}' chk
docker rm -f chk > /dev/null
Output
unhealthy
healthy

Key takeaway#

Next: Debugging Containers.

Finished this lesson?

Some links are affiliate links. If you buy through them, Laviyx Learn may earn a commission at no extra cost to you. As an Amazon Associate, we earn from qualifying purchases.