Resource limits

Docker · Advanced · 10 min

Without limits, one leaky container can starve every other service on the host.

By default a container may use all of the host's CPU and memory. Limits are enforced by the kernel's control groups, and you set them on docker run (or in Compose):

  • --memory 512m: hard memory limit. If the container exceeds it, the kernel's OOM killer kills a process in it, usually the main one, and the container exits with code 137. docker inspect -f '{{.State.OOMKilled}}' confirms it.
  • --memory-swap: memory plus swap. Setting it equal to --memory disables swap for the container.
  • --memory-reservation: a soft limit applied when the host is under memory pressure.
  • --cpus 1.5: at most 1.5 CPUs' worth of time. Exceeding it means throttling (slower), not a kill.
  • --cpu-shares: a relative weight that only matters when CPUs are contended.
  • --cpuset-cpus 0,1: pin to specific cores.
  • --pids-limit 200: cap the number of processes, which stops fork bombs.

docker stats shows live usage against limits. docker update --memory 1g api changes limits on a running container.

Make the runtime aware of its limits. Modern JVMs (container support is on by default) and Go read cgroup limits, though Go's GOMAXPROCS only became container-aware in recent versions. Node sizes its heap from container memory in recent versions too, but --max-old-space-size is still common. Many apps size worker pools from the host's CPU count, so set worker counts explicitly.

In Compose, use deploy.resources.limits (cpus, memory) and optionally reservations; docker compose up applies them.

Set limits from measurements: watch real usage under load, then add headroom. A limit that's too tight produces mysterious exit 137 restarts.

Example

docker run -d --name api --memory 512m --memory-swap 512m \
  --cpus 1.5 --pids-limit 200 acme/api:1.4.2

docker stats --no-stream api
# NAME  CPU %   MEM USAGE / LIMIT    MEM %   PIDS
# api   37.2%   311MiB / 512MiB      60.7%   23

docker inspect api --format '{{.State.ExitCode}} OOM={{.State.OOMKilled}}'
# 137 OOM=true   (after a memory spike)

docker update --memory 1g --memory-swap 1g api

# compose.yaml equivalent:
#   deploy:
#     resources:
#       limits: { cpus: "1.5", memory: 512M }

Remember

  • Default is unlimited; set --memory, --cpus and --pids-limit for production
  • Memory over the limit = OOM kill (exit 137); CPU over the limit = throttling
  • Make runtimes and worker pools aware of the limits

Watch out for

  • Setting a memory limit without telling the JVM, Node or worker pool, so the app is repeatedly OOM-killed.

Check yourself

  1. A container exceeds its --cpus 1 limit under load. What happens?
  2. A container keeps exiting with code 137 and OOMKilled=true. What does that indicate?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.docker.com/engine/containers/resource_constraints/