Cgroups and Namespaces: How Containers Actually Work
Linux namespaces isolate what a process can see; cgroups limit what it can use. Together they're the kernel primitives that make a container a container.
Cgroups and namespaces are the two Linux kernel features that make containers possible: namespaces control what a process can see — its own process list, filesystem, network interfaces — and cgroups control what it can use — how much CPU, memory, and I/O it’s allowed to consume. Neither one is a container by itself. A container runtime like Docker or containerd is really just a program that wires these two primitives together around an ordinary process and calls the result “isolated.”
Namespaces: what a process can see
A namespace wraps a global kernel resource so that processes inside the namespace see their own private view of it, while processes outside see the normal, unwrapped version. Linux provides several kinds:
- PID namespace — processes inside see their own process ID tree, starting at PID 1, unaware of any processes running outside the namespace.
- Network namespace — its own network interfaces, routing table, and port space, so a container can bind to port 80 without conflicting with the host’s own port 80.
- Mount namespace — its own view of the filesystem hierarchy, which is what lets a container have a root filesystem completely different from the host’s.
- UTS namespace — its own hostname, independent of the host machine’s.
- IPC namespace — its own inter-process communication resources (shared memory, message queues), isolated from the host’s.
- User namespace — lets a process appear to run as root inside the namespace while actually mapping to an unprivileged user on the host, narrowing what damage a compromised “root” process inside a container can actually do.
A process can be placed in a mix of these namespaces independently — a container typically gets all of them, but the kernel doesn’t require that.
Cgroups: what a process can use
Where namespaces are about visibility, control groups (cgroups) are about resource accounting and limits. A cgroup can cap how much CPU time a group of processes gets, how much memory they can allocate before being throttled or killed, how much disk and network I/O bandwidth they can consume, and how many total processes they’re allowed to spawn.
This is what stops one noisy container from starving every other workload on the same host. Without cgroup limits, a memory leak or a runaway loop in one container could consume all available RAM or CPU on the machine, taking down unrelated workloads that happen to share the host. Kubernetes resource requests and limits are, under the hood, translated directly into cgroup configuration on each node.
Putting them together: a container is just a process
Run docker run and the runtime does roughly this: it creates a new set of namespaces, sets up a cgroup with the requested resource limits, mounts the container image’s filesystem into the new mount namespace, and then executes the requested command inside that combination. From the kernel’s point of view, there’s no special “container” object — just a regular process (or process tree) that happens to be namespaced and cgrouped. Tools like docker ps are essentially inspecting that same process tree with extra bookkeeping layered on top.
Namespaces vs cgroups
| Namespaces | Cgroups | |
|---|---|---|
| Controls | Visibility / isolation | Resource limits |
| Answers | ”What can this process see?" | "How much can this process use?” |
| Examples | PID, network, mount, user | CPU, memory, I/O, process count |
| Failure mode if missing | Processes can see and interfere with each other | One process can starve the whole host |
Why this matters for security
Because every container on a host shares the same kernel, namespaces and cgroups are the only thing standing between one container and another — there’s no hypervisor boundary underneath, unlike a virtual machine. A kernel bug that lets a process escape its namespace, or a misconfiguration that leaves a container running with excessive capabilities, can turn into a container escape that reaches the host or a sibling container directly.
This is also why user namespaces matter more than they might seem to: without one, a process running as root inside a container really is root as far as the kernel is concerned if it manages to break out, whereas a properly configured user namespace maps that “root” to an unprivileged host user, containing the blast radius of an escape. Understanding this layered model is also why NUMA and other hardware-adjacent concepts matter for containerized workloads — cgroup CPU limits interact with how the scheduler places processes across physical cores and memory nodes.
The takeaway
A container isn’t a kernel-level object — it’s an ordinary process wrapped in namespaces for isolation and a cgroup for resource limits. Namespaces answer what the process can see; cgroups answer what it can use. Every container runtime, from Docker to the one Kubernetes uses on each node, is ultimately just orchestrating these two primitives, which is also why the shared kernel underneath them is the actual security boundary worth taking seriously.
Tagged
Keep reading
Chisato · · 4 min read Virtual Machines vs Containers: Key Differences
VMs virtualize hardware with a full guest OS per instance; containers share the host kernel and isolate processes. What that trade-off costs and buys you.
Chisato · · 5 min read Docker Networking Modes: Bridge, Host, and Overlay
Docker's default bridge network isolates containers with NAT; host mode shares the host's network stack; overlay connects containers across hosts.
Chisato · · 4 min read Docker vs Podman: Container Runtimes Compared
Docker runs containers through a persistent root daemon; Podman runs them daemonless and rootless by default. What that architectural split changes.