What a Container Really Is
Summary
- A container is an ordinary Linux process (or process tree) that the kernel isolates and constrains using namespaces and cgroups.
- Insight: There is no "container" object in the Linux kernel. A container is a bundle of kernel features applied to a process, plus a root filesystem.
- Key Concepts: namespaces (what a process can see), cgroups (what it can use), root filesystem (what code and libraries it runs), capabilities and seccomp (what syscalls it may make).
- When to Use: Package an app with its userspace dependencies and ship the same artifact from a laptop to production without dragging along a full guest OS.
- Limitations/Trade-offs: Containers share the host kernel, so isolation is weaker than a virtual machine and kernel-level exploits can cross the boundary.
- Related Topics: Namespaces and cgroups, the OCI specs, and how containers differ from VMs.
Foundations
Start by deleting the "tiny VM" picture from your head.
A running container is a process on the host, visible in the host's process table like any other.
Run ps aux on a Kubernetes node and you will see the application processes of every pod, right next to kubelet and containerd.
What makes that process a "container" is the kernel context it runs in.
The kernel gives the process private views of system resources through namespaces, and it caps the resources the process can consume through cgroups.
Layered on top is a root filesystem unpacked from an image, so the process sees its own /usr, /lib, and /etc instead of the host's.
That combination - isolated process, private filesystem, bounded resources - is the entire trick.
There is no hypervisor, no emulated hardware, and no second kernel.
The container calls directly into the same host kernel that everything else on the machine uses.
Mechanics & Interactions
When a runtime starts a container, it performs a sequence of ordinary syscalls.
It calls clone() (or unshare()) with flags like CLONE_NEWPID, CLONE_NEWNET, and CLONE_NEWNS to place the new process in fresh namespaces.
It writes into the cgroup filesystem under /sys/fs/cgroup to set CPU and memory limits.
It uses pivot_root to swap the process onto the image's root filesystem, then drops Linux capabilities and applies a seccomp profile.
Finally it execs your entrypoint as PID 1 inside the PID namespace.
Each namespace virtualizes one dimension.
The PID namespace makes your entrypoint believe it is PID 1 and hides other processes.
The network namespace gives the container its own interfaces, routing table, and loopback.
The mount namespace isolates the filesystem tree; the UTS namespace isolates hostname; the IPC namespace isolates shared memory; the user namespace can remap UID 0 inside to an unprivileged UID outside.
Cgroups are the other half.
A memory cgroup enforces a hard ceiling, and the kernel OOM-kills the process when it exceeds the limit rather than swapping the host to death.
A CPU cgroup uses shares and quotas to throttle the process under contention.
In Kubernetes this is exactly how resources.requests and resources.limits become real - the kubelet translates them into cgroup values via the container runtime.
Speaking of runtimes, it helps to know who does what.
On a Kubernetes node, the kubelet talks to containerd over the CRI, and containerd invokes runc to do the actual clone/cgroup/pivot_root dance.
Docker Engine follows the same lower stack (containerd plus runc) but adds build, Compose, and a developer-facing CLI on top.
You use Docker to build images and run them locally; containerd runs the pods in the cluster.
Advanced Considerations & Applications
Because containers share one kernel, the kernel is your blast radius.
A container that breaks out - through a kernel bug, an over-broad capability, or a mounted host path - lands on the host with whatever privileges it escaped with.
This is why production defaults matter: run as a non-root UID, drop all capabilities and add back only what you need, keep the read-only root filesystem where possible, and apply the restricted Pod Security Standard.
For genuinely hostile or multi-tenant workloads, you can swap the runtime for a sandbox like gVisor or Kata Containers, which put a user-space kernel or a lightweight VM between the workload and the host.
The shared kernel also has practical consequences for portability.
A container image carries userspace, not a kernel, so a container built for Linux needs a Linux kernel to run.
"Docker on a Mac" or Windows works by running a small Linux VM underneath; the container itself is still a Linux process, just inside that VM.
Image architecture matters too - an amd64 image will not run on an arm64 node without emulation, which is why multi-arch images exist.
The mental model pays off during debugging.
If a process is OOM-killed, look at its memory cgroup limit, not the host's free memory.
If a container "sees" the wrong CPU count, remember many runtimes still report host CPUs to the process, so JVMs and thread pools may need explicit sizing.
If networking fails, reason about the network namespace and its virtual interfaces, not a virtual NIC on a VM.
Common Misconceptions
"A container is a lightweight virtual machine." No - a VM emulates hardware and boots its own kernel; a container is a host process with no kernel of its own.
"Containers are secure by default." They isolate by default, but a root container with default capabilities is a soft boundary; hardening is a deliberate choice you must make.
"Docker runs my pods in Kubernetes." No - containerd (via the CRI) and runc run pods; dockershim was removed in Kubernetes 1.24 and is long gone.
"Each container has its own OS." It has its own userspace filesystem, but every container on a node shares the single host kernel.
"The image includes a kernel." It does not - an OCI image is layers of files plus config; the kernel always comes from the host.
FAQs
Is a container just a process? Essentially yes - a process (or tree) wrapped in namespaces and cgroups with an image-provided root filesystem.
Why can containers start in milliseconds? There is no kernel boot or hardware initialization; the runtime just sets up namespaces and execs your binary.
Can two containers share memory or PIDs? Only if you explicitly share a namespace, which pods do for the network namespace and optionally the PID namespace.
Does a container need an init process? For simple apps, no, but a minimal init (or --init) reaps zombies and forwards signals correctly when your app spawns children.
Are Windows containers the same idea? Conceptually yes, but they use Windows kernel isolation primitives and require a Windows host or Hyper-V isolation.
Why does my container see all host CPUs? Cgroup limits constrain scheduling, but /proc often still reports host topology, so size runtimes from limits explicitly.
Related
- Container Basics
- Linux Namespaces & cgroups
- Containers vs Virtual Machines
- OCI Image & Runtime Specs
- Immutable Infrastructure Principle
Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).