Skip to content
Menu

Virtualization6 min read

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.

console
console

      $ 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.

console
console

      $ docker run --cpu-shares 512 myapp
    
  • Default: 1024
  • A container with 512 shares gets half the CPU time of a container with 1024 shares — 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.

console
console

      $ 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 --cpus for 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.

console
console

      $ 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.

console
console

      $ 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.

console
console

      $ 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-swap equal to --memory = no swap
  • If --memory-swap is 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

console
console

      $ 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.

console
console

        $ 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.

console
console

        $ 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

console
console

      $ 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 with EAGAIN
  • The container is not killed — new process creation is simply denied
Without PID Limit
console
console

          $ docker run ubuntu bash -c ":(){ :|:& };:"

          Fork bomb consumes all PIDs on the HOST
System becomes unresponsive
May require hard reboot
        
With PID Limit
console
console

          $ 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.

console
console

    $ 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)

console
console

        $ 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

console
console

        $ 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:

bash
{
  "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 failsEAGAIN 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

  1. CPU Exceeded

    THROTTLED
    Processes paused, never killed

  2. Memory Exceeded

    OOM KILLED
    Process terminated by kernel

  3. I/O Exceeded

    THROTTLED
    I/O operations delayed

  4. PIDs Exceeded

    FORK DENIED
    EAGAIN, existing processes unaffected

Solidnines — solidnines.com