Skip to content
Expert LinuxLesson 26 of 27 4 min read

Containers From First Principles

The kernel features - namespaces and cgroups - that Docker and Kubernetes are built on.

On this page8 sections

The history lesson mentioned that Docker "packaged two kernel primitives into an easy developer workflow." This lesson is what those two primitives actually are - because a container is not a lightweight virtual machine, and understanding the real mechanism explains almost everything about how containers behave.

A container is not a VM

A virtual machine runs a full, separate kernel on top of virtualized hardware - genuinely isolated, but heavy. A container runs on the exact same kernel as its host; there is no second kernel booting anywhere. What makes it look like an isolated machine is entirely the kernel pretending, very convincingly, that a normal process can't see or touch anything outside its container. That illusion is built from two features.

Namespaces: what a process can SEE

A namespace wraps a process so that, within it, some global resource appears to be its own private copy - even though everyone is still sharing the one real kernel:

NamespaceIsolates
PIDProcess IDs - a containerized process can be PID 1 inside its own namespace, unaware of every other process on the host
NETNetwork interfaces, IP addresses, routing tables - a container gets what looks like its own network stack
MNTMount points - a container sees its own filesystem tree, not the host's
UTSHostname - a container can have its own hostname, independent of the host's
IPCInter-process communication - message queues and shared memory don't leak between namespaces
USERUser and group IDs - "root" inside a container can map to an unprivileged user outside it

unshare and nsenter are the raw tools that create and enter namespaces directly - Docker calls the same kernel APIs those wrap, just automatically, every time you run docker run.

cgroups: what a process can USE

Control groups (cgroups) are the second half: they limit and account for resources - CPU time, memory, disk I/O, network bandwidth - for a group of processes, instead of just hiding things from view. A container's memory and CPU limits (docker run --memory=512m) are cgroup rules under the hood. This is also how the kernel enforces the OOM killer decision when a container tries to use more memory than its cgroup allows: the offending process inside it gets killed, without touching anything outside that cgroup.

The third piece: a union filesystem

Namespaces and cgroups explain isolation and limits, but not images - the ability to layer a small Alpine base, then add your app on top, and ship just the diff. That's a union filesystem (commonly OverlayFS on Linux): multiple read-only layers stacked underneath one writable layer on top. Each image layer is one of those read-only slices; a running container only ever writes into its own thin top layer, which is why deleting a container leaves the shared, cached base layers untouched for the next one.

chroot: the ancestor of the idea

Long before namespaces existed, chroot gave a process a different apparent root directory - a primitive, partial ancestor of the mount namespace, dating back to 1979 Unix. chroot alone is trivially escapable and was never meant as real security isolation; namespaces and cgroups are what turned "a different-looking root directory" into "a genuinely contained process."

Putting it together

docker run --memory=512m myapp
                  │
                  ├── new PID, NET, MNT, UTS, IPC, USER namespaces  (isolation)
                  ├── a cgroup capping memory at 512MB               (limits)
                  └── myapp's image, mounted via OverlayFS            (filesystem)

Every "container" is really just: an ordinary Linux process, wrapped in namespaces it can't see past, metered by a cgroup it can't exceed, and rooted at an OverlayFS mount it thinks is the whole disk.

Try it

Run these in the practice terminal (opens in a new tab). It opens in a new tab, so you keep your place here.
bash
uname -a
ps aux
chroot /some/path
unshare --pid echo hi
docker run alpine

Further reading

  • "How Linux Works" by Brian Ward (No Starch Press) - covers namespaces and cgroups directly, as kernel features independent of any specific container tool.
  • "The Cathedral and the Bazaar" by Eric S. Raymond, and the Linux history lesson in this course - for the "why did this get built" context: Docker's 2013 breakthrough was packaging, not invention.

Challenge

Print the current kernel version, then list every running process - the two things a container runtime checks before it starts anything. Do both in one line joined with &&.

Want to experiment first? Try things in the practice terminal (opens in a new tab), then enter the command that solves the task here.

Quiz

Lock in the concepts. Score at least 80% to mark the lesson complete.

0 of 3 answered

Done with this one?

Skipped the challenge? You can mark the lesson complete by hand.

Your notes

Kept on this browser only.