cgroups & resource containment

What resource isolation promises — and what it does not.

Advanced14 min · lesson 2 of 25

Namespaces isolate what a process sees; cgroups (control groups) limit what it can consume. They are the second half of the container, and from a security standpoint they are your defense against denial-of-service from inside: without limits, one container can exhaust the host’s memory, peg every CPU, or fork until the process table is full, taking every other workload down with it. cgroup v2 is the current unified hierarchy.

terminal
$ docker run -d --name app \
--memory 256m --memory-swap 256m \ # hard RAM cap, no swap escape
--cpus 1.5 \ # 1.5 cores of scheduler time
--pids-limit 200 \ # cap process count (anti fork-bomb)
myapp:1.0
$ cat /sys/fs/cgroup/system.slice/docker-*/memory.max # the limit, in the kernel

The limits that stop a DoS

Three limits carry most of the weight. memory.max is a hard ceiling — breach it and the kernel OOM-kills a process in the cgroup, so set --memory-swap equal to --memory or an attacker just swaps past the cap. cpu limits cap scheduler share so one container cannot starve others. pids.max caps the number of tasks, which turns a fork bomb from a host outage into a contained failure. These are cheap and belong on every production container.

terminal
# a fork bomb inside a container WITHOUT --pids-limit can take down the host;
# with it, the container hits its own ceiling and everything else survives.
$ docker run --rm --pids-limit 100 alpine sh -c ':(){ :|:& };:' 2>&1 | head -1
sh: can't fork: Resource temporarily unavailable

What cgroups do NOT promise

cgroups bound resource usage; they are not an isolation or trust boundary. They do not stop a process from reading the kernel, escalating via a capability, or escaping — a container can be perfectly resource-limited and still own the host through a different hole. And some resources historically leaked: without careful configuration, tools inside a container may read the host’s total CPU/memory rather than their cgroup limits, which is why runtimes and libraries have to be cgroup-aware.

Limit swap, or the memory cap is theatre
Setting --memory without --memory-swap lets a container use the memory limit again in swap, quietly doubling its footprint and defeating the OOM protection. Set --memory-swap equal to --memory (or disable swap on container hosts entirely). A memory limit that can be swapped past is not a limit.