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.
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:
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}}' webstarting
healthyIt starts as starting, then becomes healthy. Now break it, by deleting the home page, so that the check fails:
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/nullunhealthy
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:
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/null52428800
limit 50MiBIf a process uses more than the limit, the kernel kills it:
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/nullExit code 137, OOMKilled trueExit 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:
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/null500000000500000000 nano-CPUs is half a core.
Limits in Compose#
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: 20sstart_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:
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/nullmap[max-file:3 max-size:10m]To set this for all containers, add it to /etc/docker/daemon.json:
{ "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
/healthendpoint. - 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
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/nullunhealthy
healthyKey takeaway#
Next: Debugging Containers.
Finished this lesson?
Related lessons
Recommended Docker and Kubernetes resources
- Docker Deep Dive by Nigel Poulton
A practical guide to how Docker works, for readers who have finished the basics.
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.