Containers from First Principles

A container feels like a lightweight virtual machine, but it isn't one — there's no guest OS, no hypervisor, no virtualization. A container is just a normal process that the Linux kernel has been told to isolate and constrain. Understanding that — namespaces for isolation, cgroups for limits, images for packaging — demystifies containers and everything Kubernetes builds on them.

Kubernetes orchestrates containers, so before going further, understand what a container actually is — because the common mental model (“a lightweight VM”) is wrong and obscures how everything works. A container is not a virtual machine; it’s a normal process isolated and constrained by Linux kernel features. This post covers the three primitives that make containers — namespaces (isolation), cgroups (resource limits), and images (packaging) — so containers stop being magic and Kubernetes’s foundation is solid.

Containers are not virtual machines

The first thing to unlearn: a container is not a lightweight VM. The distinction is fundamental:

VM:        [ app ][ guest OS + kernel ] on [ hypervisor ] on [ host OS + hardware ]   — heavy
Container: [ app process, isolated + constrained by the HOST kernel ]                 — light

This is why containers are so lightweight and fast: starting a container is essentially starting a process (milliseconds), not booting an operating system (seconds). A machine can run many containers because they share one kernel, versus few VMs (each a full OS). The trade-off: containers share the host kernel, so isolation is weaker than a VM’s separate-kernel boundary (a kernel vulnerability could cross containers) — which is why security-sensitive multi-tenant setups sometimes still use VMs, or lightweight VMs, around containers. But for packaging and running applications, the container’s “just an isolated process” model is what makes it fast and dense. So: a container is a process the kernel isolates and constrains. Now, how does the kernel do that — the two primitives:

Namespaces: isolation

Namespaces are the Linux kernel feature that gives a container its isolation — making a process see only its own slice of the system, as if it had the machine to itself. The kernel provides several kinds of namespace, each isolating one type of resource:

Together, namespaces make a process believe it’s alone on the machine: its own processes, network, filesystem, hostname. That illusion of isolation — “this process sees only its own world” — is containment. A container is a process (or group) placed in a set of namespaces so it’s isolated from everything else, even though it’s running right alongside other processes on the same kernel. Namespaces are what isolates a container.

Cgroups: resource limits

Isolation (namespaces) says “you can only see your own stuff.” Cgroups (control groups) say “you can only use this much.” Cgroups are the kernel feature that constrains a container’s resource consumption:

Cgroups are why you can safely pack many containers onto one machine: each is bounded in what it can consume, so a runaway or greedy container can’t hog the whole machine and starve its neighbors. This is exactly the requests and limits you set in Kubernetes (a later post) — those become cgroup constraints on the container. So the pair is: namespaces isolate (what a container can see), cgroups constrain (what it can use). Together, a container is a process that’s isolated (namespaces) and resource-limited (cgroups) by the kernel — no VM, no magic, just kernel features applied to a process.

Images: packaging

The third piece is the container image — how a container’s filesystem and app are packaged and distributed. An image is a bundle containing the application, its dependencies, libraries, and a minimal filesystem — everything the container needs to run, packaged into a portable artifact:

When a container runs, its mount namespace gives it the image’s filesystem as its root — so the “isolated filesystem” a container sees is the image’s contents. The image provides the what to run (app + filesystem), and namespaces/cgroups provide the how it runs (isolated + constrained). A running container = an image’s filesystem, executed as a process, isolated by namespaces and limited by cgroups.

Containers, demystified

Putting it together, a container is: a process running the contents of an image (its packaged app + filesystem), isolated by namespaces (its own processes/network/filesystem view) and constrained by cgroups (bounded CPU/memory), all on the host’s shared kernel — not a VM. This is what Kubernetes orchestrates. When Kubernetes “runs a container,” it’s telling a node’s container runtime to pull an image and start a process in appropriate namespaces with cgroup limits. Understanding this makes Kubernetes concrete: pods, resource limits, networking, and volumes (later posts) are all Kubernetes managing these container primitives across a cluster. Containers aren’t magic — they’re kernel features applied to processes, packaged as images. The next post covers Kubernetes’s own atom, which wraps containers: the pod.

Key takeaways

Further reading

Sources & References