Resource limits cheatsheet.

Memory

docker run --memory=512m app
docker run --memory=2g --memory-reservation=1g app
docker run --memory=2g --memory-swap=2g app          # no swap (swap = memory)
docker run --memory=2g --memory-swap=-1 app          # unlimited swap
docker run --memory=2g --memory-swappiness=0 app     # don't swap

CPU

docker run --cpus=1.5 app                            # 1.5 cores
docker run --cpu-shares=512 app                      # relative weight (default 1024)
docker run --cpuset-cpus=0,1 app                     # pin to cores 0 and 1
docker run --cpu-period=100000 --cpu-quota=50000 app # = --cpus=0.5

--cpus is the modern interface.

PIDs

docker run --pids-limit=100 app

Prevents fork bombs.

ulimits

docker run --ulimit nofile=65536:65536 app
docker run --ulimit nproc=2048 app

I/O (cgroup v1, deprecated)

docker run --blkio-weight=300 app
docker run --device-read-bps=/dev/sda:10mb app

cgroup v2 (newer kernels) handles differently.

Compose

services:
  web:
    deploy:
      resources:
        limits:
          cpus: "1"
          memory: 512M
          pids: 100
        reservations:
          cpus: "0.5"
          memory: 256M

deploy.resources is honored by Swarm and docker compose up.

OOM behavior

When container hits memory limit:

  1. Kernel tries to reclaim.
  2. If can’t → OOM killer kills a process inside container.
  3. If that was PID 1 → container exits (137).
docker inspect --format='{{.State.OOMKilled}}' web
docker inspect --format='{{.State.ExitCode}}' web    # 137 = SIGKILL

Disable OOM killer (rarely needed)

docker run --oom-kill-disable app

If memory exhausted, container hangs. Usually worse than killing.

Monitor

docker stats
docker stats --no-stream
docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"

/proc inside container

docker exec web cat /sys/fs/cgroup/memory.max     # cgroup v2
docker exec web cat /sys/fs/cgroup/cpu.max

Many monitoring tools read these.

JVM / Node tuning

JVM and Node ignore cgroup limits by default (older versions):

# JVM (Java 11+ ok)
java -XX:+UseContainerSupport ...

# Node
NODE_OPTIONS="--max-old-space-size=1024" node app.js

Always set explicit limits inside the app to match cgroup.

Right-sizing

  1. Measure with docker stats under load.
  2. Set limits 20-30% above observed peak.
  3. Set requests to typical usage (K8s resources.requests).

Too tight → frequent OOM. Too loose → resource waste.

Sandboxed CI

docker run \
  --rm \
  --memory=512m \
  --cpus=1 \
  --pids-limit=50 \
  --network=none \
  --read-only --tmpfs /tmp \
  --user 1000:1000 \
  untrusted-image

Stack the limits for untrusted code.

–shm-size

docker run --shm-size=1g chrome

Default /dev/shm is 64MB. Increase for Chrome / Postgres.

–tmpfs

docker run --tmpfs /run --tmpfs /tmp:size=64m,exec app

In-memory mount. Faster than disk for ephemeral data.

Read-only root FS

docker run --read-only --tmpfs /tmp app

Reduces attack surface. App must not write outside specified mounts.

Per-process limit (rlimit)

docker run --ulimit nofile=65536 nginx

For Nginx with many connections, raise nofile.

Cgroup v1 vs v2

Most modern Linux uses cgroup v2. Check:

mount | grep cgroup
# cgroup2 on /sys/fs/cgroup

Docker 20.10+ supports v2.

Common mistakes

  • No memory limit → one container kills the host.
  • Limit much lower than app needs → constant OOM.
  • JVM/Node without explicit max heap.
  • Forgetting --shm-size for Chrome.
  • --cpus=0.1 → app constantly throttled, slow.

Read this next

If you want my container sizing playbook, it’s at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .