Resource Limits and Cgroups
By default, a container can use all of the host's memory and CPU. If one container has a memory leak or gets hit with heavy traffic, it can starve other containers and crash the entire server. Resource limits prevent this by capping what each container can consume.
Never run a container in production without a memory limit. A single runaway container can take down everything on the host.
How Resource Limits Work
Docker uses Linux cgroups (control groups) to enforce resource limits. When a container exceeds its limits, the kernel steps in:
flowchart TD
A["Container Process"] --> B{"Memory usage"}
B -->|"Below limit"| C["Normal operation"]
B -->|"At limit"| D["OOM Killer invoked"]
D --> E["Process killed (exit code 137)"]
A --> F{"CPU usage"}
F -->|"Below limit"| G["Normal operation"]
F -->|"At limit"| H["CPU throttled (slowed down)"]
style C fill:#e8f5e9,stroke:#2e7d32
style G fill:#e8f5e9,stroke:#2e7d32
style E fill:#ffebee,stroke:#c62828
style H fill:#fff3e0,stroke:#ef6c00
The key difference: memory limits are hard (exceed → killed), CPU limits are soft (exceed → throttled/slowed).
Memory Limits
| Flag | What It Does | Example |
|---|---|---|
--memory / -m | Hard memory limit. Container is killed (OOM) if it exceeds this | --memory="512m" |
--memory-swap | Total RAM + swap limit. Set to same as --memory to disable swap | --memory-swap="512m" |
CLI Example
docker run -d --name api --memory="512m" my-api:1.0.0
Compose Example
services:
api:
image: my-api:1.0.0
deploy:
resources:
limits:
memory: 512M
reservations:
memory: 256M
What Happens When the Limit Is Reached?
The Linux kernel's OOM Killer terminates the container's main process. The container exits with code 137. You can check if this happened:
docker inspect -f '{{.State.OOMKilled}}' my-container
# true = killed by OOM
CPU Limits
| Flag | What It Does | Example |
|---|---|---|
--cpus | Maximum CPU cores the container can use (fractional) | --cpus="1.5" (1.5 cores) |
--cpuset-cpus | Pin the container to specific CPU cores | --cpuset-cpus="0,1" |
CLI Example
# Limit to half a CPU core
docker run -d --name worker --cpus="0.5" my-worker:1.0.0
Unlike memory limits, hitting a CPU limit does not kill the container. Instead, the process is throttled -- it runs slower. This can cause latency spikes that look like application bugs.
Process (PID) Limits
A container can spawn unlimited processes by default. A fork bomb or runaway process spawner can exhaust the host's PID space:
docker run -d --name api --pids-limit=256 my-api:1.0.0
This limits the container to 256 concurrent processes.
Monitoring Resource Usage
Live Stats
docker stats
Output:
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % PIDS
a1b2c3d4e5f6 api 0.05% 12.5MiB / 512MiB 2.44% 15
f6e5d4c3b2a1 worker 45.30% 480MiB / 512MiB 93.75% 42
Watch for: memory usage consistently above 80% of the limit (risk of OOM), or CPU at 100% of its allocation (throttling).
Inspect Current Limits
# Memory limit (in bytes)
docker inspect -f '{{.HostConfig.Memory}}' my-container
# CPU limit (in nanocpus: 1 CPU = 1000000000)
docker inspect -f '{{.HostConfig.NanoCpus}}' my-container
# OOM killed?
docker inspect -f '{{.State.OOMKilled}}' my-container
Choosing Initial Limits
| Service Type | Memory Starting Point | CPU Starting Point |
|---|---|---|
| Node.js web server | 256m - 512m | Usually leave unlimited |
| Python web server | 256m - 512m | Usually leave unlimited |
| Go web server | 64m - 256m | Usually leave unlimited |
| PostgreSQL | 512m - 2g | 1.0 - 2.0 |
| Redis | 128m - 512m | Usually leave unlimited |
| Worker / batch job | Task-dependent | Task-dependent |
Tuning Workflow
- Observe baseline usage under normal traffic using
docker stats - Set initial limit at ~2x the observed baseline
- Monitor for OOM kills or throttling after deploying
- Adjust one variable at a time based on data
- Document the final values and rationale
Complete Example
# Run with memory, CPU, and PID limits
docker run -d \
--name api \
--memory=512m \
--cpus=1.0 \
--pids-limit=256 \
--restart unless-stopped \
my-api:1.0.0
# Monitor usage
docker stats api
# Check for OOM after an incident
docker inspect -f '{{.State.OOMKilled}}' api
Common Mistakes
| Mistake | Consequence | Fix |
|---|---|---|
| No memory limit in production | One container can crash the entire host | Always set --memory |
| Limit set too low | Constant OOM kills under normal traffic | Observe baseline first, set 2x headroom |
| Same limits for all services | Workers need different profiles than APIs | Tune per service type |
| Ignoring OOM events | Memory leak goes undetected | Monitor OOM flag and alert on kills |
| Very tight CPU limits | Latency spikes that look like application bugs | Monitor latency after setting CPU limits |
Key Takeaways
- Memory limits are mandatory for production -- without them, one container can crash your server.
- Memory exceeding the limit → process killed (OOM). CPU exceeding the limit → process slowed (throttled).
- Use
docker statsto observe baseline usage, then set limits with headroom. - Check
OOMKilledflag after unexplained container exits. - Tune limits iteratively based on real data, not guesses.
What's Next
- Continue to Entrypoint, CMD, and Signal Handling to design containers that start and stop correctly.