JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

Containers: Namespaces and cgroups

A virtual machine fakes a whole computer; a container fakes a whole computer's worth of illusion using nothing but features of the one kernel you already have. This guide opens the box and shows the two plain mechanisms inside — namespaces that change what a process can see, and cgroups that limit what it can use — and the layered image that lets a thousand containers share one set of files.

No second OS at all

The last three guides built a complete illusion of hardware. A hypervisor presents fake CPUs, fake memory, and fake devices so that an entire guest operating system, kernel and all, can run inside a virtual machine believing it owns the metal. That illusion is strong, but it is also heavy: every VM carries its own full copy of an OS, boots like a real machine, and reserves its own slice of RAM whether it needs it or not. Now we change the question. What if you do not want to fool a whole guest kernel — you only want to fool the processes?

That is the whole idea of a container. A container is just one or more ordinary processes running on your normal kernel, with no guest OS underneath them at all — but they have been wrapped so that each one sees its own private little world. To the program inside, it looks like it is alone on a fresh machine: its own process IDs starting from 1, its own root filesystem, its own network interfaces, its own hostname. To the kernel underneath, it is just another set of processes it schedules right alongside everything else. This is OS-level virtualization: the kernel itself hands out per-process illusions, instead of a hypervisor handing out per-machine ones.

Namespaces: changing what a process can see

The first of the two real mechanisms is the namespace. Normally, every process on a machine shares one global view of certain kernel-managed things: there is one master list of process IDs, one tree of mounted filesystems, one set of network interfaces, one table of hostnames. A namespace takes one of those global lists and gives a group of processes their own private copy of it. Inside a process-ID namespace, for instance, the processes see IDs renumbered starting at 1, and cannot even name — let alone signal or kill — any process outside their namespace. It is not that the other processes are protected; they are simply invisible.

There is not one kind of namespace but several, each carving up a different global resource, and a container is built by combining them. A mount namespace gives the container its own filesystem tree, so its idea of where the root directory and each mount point live is entirely its own. A network namespace gives it its own interfaces and ports. A PID namespace renumbers its processes. A UTS namespace gives it its own hostname. A user namespace can even let a process be the all-powerful root user inside the container while being a harmless unprivileged user outside it. Each namespace is independent, so you mix and match exactly the slices of the world you want a container to have its own copy of.

Crucially, none of this is a copy of the kernel — it is bookkeeping inside the one kernel. When a process asks the kernel for the list of processes, the kernel checks which PID namespace that process belongs to and answers with only that namespace's entries. When the process opens a file path, the kernel resolves it against that process's mount namespace. The work is the same work the kernel already did to manage these resources; the namespace just adds a label saying which private view each process should be answered from. That is why creating a container is nearly instant: there is no machine to boot, only a few namespaces to allocate and a process to start.

cgroups: limiting what a process can use

Namespaces decide what a container can see, but they say nothing about how much it can consume. A process that sees only its own private world can still, left unchecked, hog every CPU core and eat all the RAM on the host, starving its neighbours. The second mechanism fixes that: control groups, almost always written cgroups. A cgroup is a labelled group of processes to which the kernel attaches resource limits and resource accounting. You put a container's processes into a cgroup and say, in effect: this group may use at most 2 CPUs' worth of time, at most 512 MB of memory, and at most this much disk bandwidth.

These limits hook directly into the kernel subsystems you already understand from earlier rungs. The CPU limit leans on the scheduler: the scheduler already decides which runnable process gets the CPU next, so making it honour a cgroup's CPU share is just one more rule in the same decision. The memory limit leans on the memory manager: when the processes in a cgroup together hold more than their allowance, the kernel refuses further allocations or reclaims their pages, exactly the page-reclaim machinery from the virtual-memory rung, but scoped to that group. cgroups are not a new kind of resource control; they are the kernel's existing scheduling and memory accounting, partitioned per group.

One host kernel, two containers:

  Namespaces (what each can SEE)          cgroups (how much each can USE)
  --------------------------------        -------------------------------
  container A: own PIDs, own mounts,       group A: <= 2 CPUs, <= 512 MB
               own net, hostname "a"
  container B: own PIDs, own mounts,       group B: <= 1 CPU,  <= 256 MB
               own net, hostname "b"

  ----------------- the single shared kernel does all the work -----------------
Two orthogonal tools: namespaces partition visibility, cgroups partition consumption, and both are enforced by the one kernel they share.

The overlay image: sharing files in layers

There is one more piece. A container needs a root filesystem — the files that make up its private world, the programs and libraries it will run. Giving each container its own full copy of, say, a multi-hundred-megabyte base system would throw away most of the lightness we just won. The answer is the overlay filesystem, and it is the storage cousin of the copy-on-write trick from the virtual-memory rung. A container image is built as a stack of read-only layers: a base layer (a minimal OS userland), then a layer adding a language runtime, then a layer with your application. An overlay filesystem merges this stack into one filesystem view, and crucially, the read-only layers are shared by every container built from them.

On top of the shared read-only layers, each running container gets one thin writable layer of its own. When the container reads a file, the overlay searches down through the layers and serves it from wherever it first appears — usually a shared read-only layer, costing no extra space. When the container writes a file, the overlay first copies that single file up into the container's private writable layer, and the write lands there; the shared layer underneath is never touched. This is copy-on-write at the granularity of files: a hundred containers from the same image share one physical copy of the base, and only the bytes they actually change ever get duplicated.

  1. Start a container: the kernel creates its namespaces, places its processes in a cgroup with limits, and mounts the overlay as its root.
  2. The container reads a library: the overlay finds it in a shared read-only layer and serves it directly, costing no extra disk.
  3. The container writes to a config file: the overlay copies just that one file up into the container's private writable layer, then performs the write there.
  4. The container exits: discard its thin writable layer; the shared read-only layers remain, untouched, for the next container.

From one container to a fleet

Put the three pieces together and a container stops looking mysterious. It is a normal process (or small group of processes), wrapped in namespaces so it sees its own world, fenced by cgroups so it cannot overrun its share, and rooted on an overlay filesystem so it gets its files almost for free. There is no emulation, no trap-and-emulate, no second kernel — just the host kernel doing its ordinary scheduling, memory management, and file serving, but answering each container from that container's own labelled view. Starting one is a matter of milliseconds and a few megabytes, which is why a single host can comfortably run dozens or hundreds of them.

Once containers are this cheap, you stop hand-managing them and let software do it. Orchestration is the layer above a single host that runs containers across a whole cluster of machines: deciding which host has room for the next container, restarting one that crashes, scaling a service from three copies to thirty when load rises, and routing traffic to wherever the copies happen to be running. The container is the lightweight unit; orchestration is the manager that schedules thousands of those units across many hosts — a higher-level echo of the very scheduling and resource management your kernel does for processes on one machine.