From Primitives to Platform
When you run docker run -m 512m --cpus 0.5 ubuntu bash, Docker performs a precise sequence of kernel operations using all three mechanisms we have covered:
1. Namespaces — create isolation:
Docker calls clone() with flags for PID, NET, MNT, UTS, IPC, and USER namespaces. The new process starts in a completely isolated environment: its own PID space (it is PID 1), its own network stack (with a veth pair connected to the Docker bridge), and its own filesystem root (the Ubuntu image layers, assembled via overlay filesystem).
2. Cgroups — enforce limits:
Docker creates a new cgroup under /sys/fs/cgroup/docker/<container-id>/ and writes the memory limit (memory.max, in bytes — 536870912 for 512 MB) and CPU quota (cpu.max, expressed as microseconds of CPU time allowed per 100ms period — 50000 100000 for 50% of one core) into the cgroup's control files. The container's process is added to this cgroup. The kernel now enforces these limits automatically.
3. ulimit — set per-process defaults:
Docker sets nofile limits on the container process (default: 1048576 in modern Docker). You can override this with docker run --ulimit nofile=65535:65535.
The result: A process that believes it is alone on a machine with its own network and filesystem, but is actually constrained to exactly 512 MB of RAM and half a CPU core, running alongside hundreds of other such processes on the same physical host.
This is also why container "escapes" are serious security vulnerabilities — they involve breaking out of one or more of these namespace boundaries to gain visibility into or control over the host or other containers.