Resource Limits
CPU, memory, I/O, and PID limits — and what happens when they're exceeded
CPU Limits
Hard Limit: --cpus
Sets the maximum number of CPU cores a container can use.
$ docker run --cpus 2.5 myapp
- Container can use at most 2.5 CPU cores
- Maps to cgroup v2:
cpu.max=250000 100000(250ms quota out of every 100ms period) - When the quota is exhausted in a period, the container is throttled — processes are paused until the next period
- Container is never killed for exceeding CPU — only throttled
--cpus 2.5 → cpu.max: 250000 100000
--cpus 0.5 → cpu.max: 50000 100000
Proportional Sharing: --cpu-shares
Relative CPU weight — only matters when CPU is contended.
$ docker run --cpu-shares 512 myapp
- Default:
1024 - A container with
512shares gets half the CPU time of a container with1024shares — only when both are competing for CPU - When no contention, containers can use all available CPU regardless of shares
- Maps to cgroup v2:
cpu.weight(converted from v1 shares scale)
Note
Note: --cpu-shares is a soft limit. Unlike --cpus, it never throttles a container
when CPU is available. Think of it as priority, not a cap.
CPU Pinning: --cpuset-cpus
Restrict a container to specific CPU cores.
$ docker run --cpuset-cpus "0,1" myapp
$ docker run --cpuset-cpus "0-3" myapp
- Container can only execute on the specified cores
- Maps to cgroup:
cpuset.cpus - Use cases: NUMA-aware workloads, latency-sensitive applications, isolating noisy neighbors
- Combine with
--cpusfor both pinning and bandwidth limiting
--cpuset-cpus "0,1" → cpuset.cpus: 0,1
Memory Limits
Hard Limit: --memory
Maximum memory the container can use. Exceeding it triggers the OOM killer.
$ docker run --memory 512m myapp
- Hard limit — maps to cgroup v2:
memory.max = 536870912(512 * 1024 * 1024) - When the container exceeds this, the kernel OOM killer kills the container process
- Docker then applies the restart policy to determine whether to restart it
- Suffixes:
b,k,m,g
Soft Limit: --memory-reservation
Best-effort limit — only enforced when the host is under memory pressure.
$ docker run --memory 512m --memory-reservation 256m myapp
- Maps to cgroup v2:
memory.low - Under normal conditions, the container can use up to
--memory - Under host memory pressure, the kernel tries to reclaim memory down to the reservation level
- Must be lower than
--memory
Swap: --memory-swap
Controls total memory + swap available to the container.
$ docker run --memory 512m --memory-swap 1g myapp
--memory-swap 1g --memory 512m= 512m RAM + 512m swap (total = swap value)--memory-swap -1= unlimited swap--memory-swapequal to--memory= no swap- If
--memory-swapis not set, swap = 2x memory (default)
--memory |
--memory-swap |
Result |
|---|---|---|
512m |
1g |
512m RAM + 512m swap |
512m |
512m |
512m RAM + 0 swap |
512m |
-1 |
512m RAM + unlimited swap |
512m |
(unset) | 512m RAM + 512m swap (2x default) |
OOM Kill Disable: --oom-kill-disable
$ docker run --memory 512m --oom-kill-disable myapp
- Prevents the OOM killer from killing the container when it exceeds
--memory - The container hangs instead — memory allocations block indefinitely
Warning
DANGEROUS: --oom-kill-disable without --memory is especially dangerous.
The container can consume all host memory, causing the host's OOM killer to kill random host processes
(including other containers, SSH, or system daemons). Always set --memory when using
--oom-kill-disable, and even then, think twice — a hung container is rarely better than a restarted one.
I/O Limits
Bandwidth Limits
Maximum bytes per second for reads/writes to a specific device.
$ docker run \
--device-read-bps /dev/sda:10mb \
--device-write-bps /dev/sda:10mb \
myapp
- Max 10 MB/s read from
/dev/sda - Max 10 MB/s write to
/dev/sda - Maps to cgroup:
io.max ... rbps=10485760 wbps=10485760
IOPS Limits
Maximum I/O operations per second.
$ docker run \
--device-read-iops /dev/sda:1000 \
--device-write-iops /dev/sda:1000 \
myapp
- Max 1000 read IOPS from
/dev/sda - Max 1000 write IOPS to
/dev/sda - Maps to cgroup:
io.max ... riops=1000 wiops=1000
Note
Important limitation: I/O limits only work with direct I/O (bypassing the page cache,
e.g., O_DIRECT). Buffered I/O goes through the page cache and is not subject to these limits.
Most applications use buffered I/O by default, so these limits may have little practical effect unless the workload
uses direct I/O (e.g., some databases with O_DIRECT enabled).
PID Limits
--pids-limit
$ docker run --pids-limit 100 myapp
- Maximum 100 processes (including threads) inside the container
- Maps to cgroup:
pids.max = 100 - Primary use case: fork bomb protection
- When the limit is hit,
fork()/clone()fails withEAGAIN - The container is not killed — new process creation is simply denied
$ docker run ubuntu bash -c ":(){ :|:& };:"
Fork bomb consumes all PIDs on the HOST
System becomes unresponsive
May require hard reboot
$ docker run --pids-limit 100 ubuntu bash -c ":(){ :|:& };:"
bash: fork: retry: Resource temporarily unavailable
Fork bomb contained to 100 processes
Host is unaffected
docker stats
Real-time resource usage monitoring — reads directly from cgroup accounting files.
$ docker stats --no-stream
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
abc123def456 web 0.07% 24.5MiB / 512MiB 4.79% 1.2kB / 648B 8.19kB / 0B 5
def789abc012 db 2.31% 384MiB / 1GiB 37.5% 2.1kB / 1.4kB 12MB / 45MB 28
| Column | Source | Description |
|---|---|---|
CPU % |
cpu.stat |
Percentage of host CPU time used |
MEM USAGE / LIMIT |
memory.current / memory.max |
Current memory vs hard limit |
NET I/O |
Network namespace counters | Total bytes received / transmitted |
BLOCK I/O |
io.stat |
Total bytes read / written to block devices |
PIDS |
pids.current |
Number of processes in the container |
Tip
Tip: docker stats (without --no-stream) gives a live-updating view,
similar to top. Use --no-stream for scripting and one-shot snapshots.
Custom Runtimes
Docker supports pluggable OCI runtimes. Instead of runc, you can use security-focused alternatives:
gVisor (runsc)
$ docker run --runtime=runsc myapp
- User-space kernel that intercepts syscalls
- Container processes never directly touch the host kernel
- Stronger isolation at the cost of performance and syscall compatibility
Kata Containers
$ docker run --runtime=kata-runtime myapp
- Runs each container inside a lightweight VM
- Full kernel isolation — each container has its own kernel
- Higher overhead than runc, but VM-level security
Runtimes are configured in /etc/docker/daemon.json:
{
"runtimes": {
"runsc": {
"path": "/usr/local/bin/runsc"
},
"kata-runtime": {
"path": "/usr/bin/kata-runtime"
}
},
"default-runtime": "runc"
}Cross-References
Note
Related: For the cgroup internals behind these flags (cpu.max, memory.max, pids.max, io.max), see Virt 03 — cgroup Controllers.
Note
Related: For Kubernetes resource limits (requests, limits, QoS classes) which build on these same cgroup mechanisms, see Virt 03 — cgroups in Practice.
Quick Reference Table
| Docker Flag | cgroup File | Behavior on Exceed |
|---|---|---|
--cpus 2 |
cpu.max 200000 100000 |
Throttled — processes paused until next period |
--cpu-shares 512 |
cpu.weight ~50 |
Gets less CPU (only when contended) |
--cpuset-cpus "0,1" |
cpuset.cpus 0,1 |
Restricted to those cores only |
--memory 512m |
memory.max 536870912 |
OOM killed — process terminated by kernel |
--memory-reservation 256m |
memory.low 268435456 |
Reclaim pressure — best-effort under contention |
--pids-limit 100 |
pids.max 100 |
Fork fails — EAGAIN error on fork()/clone() |
--device-read-bps /dev/sda:10mb |
io.max ... rbps=10485760 |
Throttled — reads delayed until bandwidth available |
--device-write-bps /dev/sda:10mb |
io.max ... wbps=10485760 |
Throttled — writes delayed until bandwidth available |
--device-read-iops /dev/sda:1000 |
io.max ... riops=1000 |
Throttled — I/O ops queued |
--device-write-iops /dev/sda:1000 |
io.max ... wiops=1000 |
Throttled — I/O ops queued |
What Happens When Limits Are Exceeded
CPU Exceeded
THROTTLED
Processes paused, never killedMemory Exceeded
OOM KILLED
Process terminated by kernelI/O Exceeded
THROTTLED
I/O operations delayedPIDs Exceeded
FORK DENIED
EAGAIN, existing processes unaffected