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:
- Kernel tries to reclaim.
- If can’t → OOM killer kills a process inside container.
- 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
- Measure with
docker statsunder load. - Set limits 20-30% above observed peak.
- 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-sizefor 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 .