Namespaces and cgroups

Docker · Expert · 15 min

Knowing the kernel primitives demystifies isolation, limits and many container bugs.

There is no "container" object in the Linux kernel. A container is an ordinary process set up with two families of kernel features.

Namespaces control what a process can see. Each kind gives the process its own view of one global resource:

  • pid: its own process tree; the app is PID 1 and can't see host processes.
  • net: its own interfaces, IPs, routing table and ports (a veth pair connects it to the bridge).
  • mnt: its own mount table, with the image's root filesystem as /.
  • uts: its own hostname. ipc: its own shared memory and semaphores.
  • user: maps UIDs inside to different UIDs outside (used by rootless mode and userns-remap).
  • cgroup and time: virtualized views of the cgroup tree and of some clocks.

Docker's flags map straight onto these: --network host and --pid host mean don't create that namespace, and --network container:x joins another container's namespace.

Control groups (cgroups) control what a process can use: memory, CPU time, I/O bandwidth and number of PIDs, with accounting for each. Modern hosts use cgroup v2, a single unified hierarchy under /sys/fs/cgroup. With the systemd cgroup driver, a container lives in a scope like system.slice/docker-<id>.scope, where files such as memory.max, cpu.max and pids.max hold the limits you set with --memory, --cpus and --pids-limit.

Since a container is just a process, the host sees it: ps on the host lists it with a host PID, and nsenter can enter its namespaces. Together with capabilities, seccomp and LSMs (AppArmor/SELinux), these features make up container isolation, all sharing one kernel.

Example

docker run -d --name web --memory 256m --cpus 0.5 nginx:1.27
PID=$(docker inspect -f '{{.State.Pid}}' web)
ps -o pid,user,cmd -p "$PID"            # visible from the host
sudo ls -l /proc/$PID/ns                # net -> net:[4026532301] ...
sudo lsns -p "$PID"

# Enter just its network namespace with host tools
sudo nsenter -t "$PID" -n ip addr

# Its cgroup v2 limits (systemd driver)
CG=$(awk -F: '{print $3}' /proc/$PID/cgroup)
cat /sys/fs/cgroup$CG/memory.max        # 268435456
cat /sys/fs/cgroup$CG/cpu.max           # 50000 100000

Remember

  • Namespaces limit what a process sees: pid, net, mnt, uts, ipc, user, cgroup, time
  • cgroups limit and account what it uses: memory, CPU, I/O, PIDs
  • --network host / container:x skip or share a namespace
  • A container is a host process; nsenter and /proc reveal it

Watch out for

  • Treating namespace isolation as a security boundary equal to a VM — all containers share one kernel.

Check yourself

  1. Which mechanism enforces --memory 256m?
  2. What does docker run --pid host do?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.docker.com/engine/security/