Skip to content
All articles

How Linux Cgroups & Namespaces Made Modern Containers Possible

Dean Jain

Dean Jain

Senior Staff Software Engineer · Enterprise AI, Data & Cloud Architect

· 5 min read

ContainersLinuxDockerFundamentals
---
config:
  theme: neutral
  fontSize: 16
---
flowchart TB
    subgraph VM["Virtual machines virtualize HARDWARE"]
        direction TB
        HW1["Hardware"]:::neutral --> HYP["Hypervisor"]:::gate
        HYP --> G1["Guest OS + App"]:::warn
        HYP --> G2["Guest OS + App"]:::warn
    end
    subgraph CON["Containers virtualize the OS"]
        direction TB
        HW2["Hardware"]:::neutral --> K["Host OS · ONE shared kernel"]:::server
        K --> C1["namespaces + cgroups  App"]:::good
        K --> C2["namespaces + cgroups  App"]:::good
        K --> C3["namespaces + cgroups  App"]:::good
    end
    classDef neutral fill:#FFFFFF,stroke:#333333,stroke-width:1px,color:#111111
    classDef gate fill:#F0F0F0,stroke:#333333,stroke-width:1px,color:#111111
    classDef warn fill:#DAE8FC,stroke:#333333,stroke-width:1px,color:#111111
    classDef server fill:#DCDCDC,stroke:#333333,stroke-width:1px,color:#111111
    classDef good fill:#E8E8E8,stroke:#333333,stroke-width:1px,color:#111111

Figure 1: VMs virtualize hardware (a full guest OS each); containers virtualize the OS (one shared kernel).

People call containers “lightweight VMs,” and that framing hides what’s actually happening. A container isn’t a tiny virtual machine there’s no hypervisor, no guest OS. It’s an ordinary Linux process running directly on the host kernel, which the kernel has been told to (a) lie to about what exists, and (b) fence in on what it can consume. Those two capabilities are real Linux features namespaces and cgroups and together they are containers. Once you see that, Docker and Kubernetes stop being magic.

Summary

  • A container is a normal process, isolated by the kernel not a VM. No hypervisor, no guest OS.
  • VMs virtualize hardware; containers virtualize the OS. Containers share the host kernel, which is why they’re so lightweight and start so fast.
  • Namespaces limit what a process can see its own process tree, network interfaces, mounts, user IDs. The kernel hands each container a private view of the system.
  • Cgroups limit what a process can use CPU, memory, network, and IO quotas. The kernel caps and meters resource consumption.
  • Docker is packaging on top. Images, the Dockerfile, the daemon, and registries make these kernel features convenient but the isolation is all kernel.

1. The real distinction: hardware vs OS virtualization

A virtual machine virtualizes hardware. A hypervisor presents fake CPUs, memory, and devices to each VM, and every VM runs a full guest operating system with its own kernel. That’s strong isolation, but it’s heavy: you pay for an entire OS per workload, and boots take real time.

A container virtualizes the operating system instead. There’s no guest OS and no second kernel every container shares the host’s single kernel. The application’s footprint is just the app and its dependencies, which is why containers are lightweight, dense, and start in milliseconds.

That shared kernel is the whole trade. You give up the hard hardware-level boundary a VM provides, and in exchange you get speed and density because isolating a process is far cheaper than emulating a computer. The kernel achieves that isolation with two mechanisms, and neither is exotic: both ship in mainline Linux and were largely driven by Google.

2. Namespaces: what a process can see

Namespaces are a kernel mechanism for limiting the visibility a group of processes has of the rest of the system. Put a process in a fresh set of namespaces and the kernel hands it a private view it genuinely cannot see what’s outside.

---
config:
  theme: neutral
  fontSize: 16
---
flowchart LR
    P["Container process"]:::server --> PID["PID namespace<br/>its own process tree"]:::obs
    P --> NET["NET namespace<br/>its own interfaces"]:::obs
    P --> MNT["MNT namespace<br/>its own filesystem mounts"]:::obs
    P --> USR["USER namespace<br/>its own user IDs"]:::obs
    classDef server fill:#FFFFFF,stroke:#333333,stroke-width:1px,color:#111111
    classDef obs fill:#F0F0F0,stroke:#333333,stroke-width:1px,color:#111111

Figure 2: Namespaces give a process a private view its own process tree, network, mounts, and user IDs.

That’s why, inside a container, ps shows only the container’s processes (its main process is even PID 1), the network looks like a machine of its own, and the filesystem appears to be a clean root all illusions the kernel maintains per-namespace. The full set of namespaces (the last major one landed in Linux 3.8) covers process trees, network interfaces, mounts, user IDs, and more. Isolation of visibility is namespaces’ entire job. They make a process think it’s alone on the machine.

3. Cgroups: what a process can use

Visibility isn’t enough a process that can’t see its neighbors can still starve them by hogging CPU or RAM. Control groups (cgroups) are the kernel mechanism for limiting and measuring the total resources a group of processes consumes:

---
config:
  theme: neutral
  fontSize: 16
---
flowchart LR
    CG["cgroup"]:::gate --> CPU["CPU quota"]:::warn
    CG --> MEM["Memory limit"]:::warn
    CG --> IO["IO bandwidth"]:::warn
    CG --> NET["Network quota"]:::warn
    classDef gate fill:#FFFFFF,stroke:#333333,stroke-width:1px,color:#111111
    classDef warn fill:#F0F0F0,stroke:#333333,stroke-width:1px,color:#111111

Figure 3: Cgroups cap and meter what a process can consume CPU, memory, IO, and network.

Apply a cgroup and you can say “this group gets at most 2 CPUs and 512 MB” the kernel enforces the ceiling and measures usage (which is also how you get per-container resource metrics). Cgroups came out of Google (merged in Linux 2.6.24), and they’re what make a host safely shareable: noisy neighbors can’t run away with the machine.

Put them together and the formula is clean: namespaces decide what a container sees, cgroups decide what it uses. That’s the container no hypervisor required.

4. So what is Docker, then?

If the kernel already does the isolation, what does Docker add? Ergonomics. Namespaces and cgroups are low-level syscalls; nobody wants to wire them up by hand. Docker packages the whole experience:

  • Images + Dockerfile a reproducible recipe for the app and its dependencies, so “works on my machine” becomes “works everywhere with the same kernel.” (Because images share the host kernel, a Linux image runs on any host with a compatible kernel.)
  • The daemon (dockerd) a client-server architecture where the Docker client sends API requests and the daemon does the heavy lifting of building, running, and distributing containers.
  • Registries where images live (Docker Hub, ECR, Artifact Registry); docker pull/push move them around.

Docker didn’t invent containers it made the kernel’s existing isolation usable. And that usability is exactly what let Kubernetes come along and orchestrate thousands of these processes across a fleet. But peel back every layer and you’re still looking at the same thing: a Linux process, made private by namespaces and bounded by cgroups.

Why it matters: treating a container as a “lightweight VM” leads to a pile of wrong intuitions about security, performance, and what isolation you actually have. The accurate model a normal process the kernel isolates with namespaces and limits with cgroups, sharing the host kernel explains why containers are fast and dense, why a kernel exploit is more dangerous than in a VM, and why resource limits are a first-class concern. Hold that model and everything above it (Docker, Kubernetes, service meshes) reads as convenience layered on two kernel features.

References