Containers & Orchestration·9 min read·

How Containers Actually Work: cgroups, Namespaces, OCI

Demystify containers by looking at the three real building blocks — cgroups for limits, namespaces for isolation, and the OCI runtime spec.

NB

Netbay Developer Relations

Netbay Engineering

On this page

A container is not a tiny virtual machine. There is no separate operating system and no emulated hardware — there is just a process on your host that cannot see most of the host and cannot use more than its allowed share of CPU or memory. That trick is built from two Linux kernel features, cgroups and namespaces, organized by a standard called the OCI runtime spec. Once you see those pieces, container engines stop being magic.

This post builds a mental model of what docker run actually does, layer by layer.

Namespaces: The Illusion of a Private Machine

A namespace hides part of the host from a process. The most important ones: mount (filesystem), pid (process tree), net (network stack), uts (hostname), ipc (interprocess comms), user (UID/GUID mapping), and cgroup (resource view). When you start a container, the runtime puts your process into fresh namespaces so it sees its own process table, its own filesystem root, and its own network interface.

bash
# shows the namespaces a couple of container processes live in
ls -l /proc/$(pgrep -x nginx)/ns/
# mnt, pid, net, uts, ipc, user, cgroup all point to private inodes
unshare --pid --fork --mount-proc sh -c "hostname && ps aux"

The harmless unshare command above creates new pid and mount namespaces for a subshell — the same primitive the runtime uses at a much larger scale.

cgroups: The Brakes and the Budget

Namespaces give isolation, but they do not limit resources. That is cgroups' job. A control group is a kernel-managed bucketer for processes, and the cpu, memory, and io controllers set hard and soft limits. The container engine places each container into its own cgroup.

bash
# read the memory limits of a running container's cgroup
cat /sys/fs/cgroup/memory.slice/memory.max
# put a shell under a 256MB memory cap with systemd
systemd-run --scope -p MemoryHigh=256M -p MemoryMax=512M   bash -c "while true; do :; done"

When the memory.max watermark is hit, the kernel starts reclaiming or OOM-kills members of that group — the container, not your whole host.

The OCI Runtime Spec: A Common Language

The Open Container Initiative defines what a "container" is so that runc, crun, containerd, Podman, and Docker all agree. The OCI runtime spec describes a bundle: a config.json plus a root filesystem. Running a container means passing that config to a runtime like runc, which reads the namespaces and cgroup settings and launches the process. The engine above (Docker, Podman) is mostly the part that builds that config, manages layers, and exposes a nice CLI.

docker / podman containerd / runc kernal: namespaces + cgroups one Linux host (your VPS) shared kernel, isolated processes

What This Means for You

Because containers share the host kernel, they start in milliseconds and use only the memory of their own processes. But they also share kernel security and uptime with the host — a good reason to run containers as non-root and keep images minimal. Tuning a memory cap is often the first fix for a flaky app: give it a tight cgroup so the kernel enforces a budget instead of letting it balloon.

Takeaway

Containers are a user-space convention on top of cgroups and namespaces, standardized by OCI. Understanding those layers makes limits, networking, and Podman's podman top output legible instead of mystical. Run your containers on an up-to-date Linux kernel from a Netbay VPS — netbayhosts.in provisions a fresh 24.04 instance in under a minute so you can experiment with namespaces and cgroups safely.

Keep reading

Follow along on a real VPS

Deploy Linux in under 60 seconds

These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.

Deploy an instance