Overview
A minimal container runtime written in C for the Operating Systems course. The
goal was not to build something anyone should deploy — it was to remove the
mystery from docker run by implementing the parts that matter.
What a container actually is
The useful realization is that a container is not a thing the kernel has. There is no container object. There is a normal process that has been lied to about what it can see, and constrained in what it can consume.
The lying is done by namespaces. A new PID namespace means the process sees
itself as PID 1 and cannot see anything outside. A mount namespace gives it its
own filesystem view, so a chroot-style root can be set up without affecting
the host. Network and UTS namespaces isolate interfaces and hostname. Each is a
flag to clone(), and together they produce something that looks like a
separate machine from the inside.
The constraining is done by cgroups. Namespaces control visibility but say nothing about resources — without cgroups an isolated process can still consume all the memory and CPU on the host. Writing limits into the cgroup filesystem is what makes isolation meaningful rather than cosmetic.
Why write it in C
Higher-level languages wrap these syscalls in libraries that make the mechanism
disappear, which defeats the purpose. In C, clone(), mount(), pivot_root()
and the cgroup writes are visible as the syscalls they are, and the ordering
constraints between them — which must happen before the child starts, which
must happen after — are impossible to skip past without understanding them.
Scope
Deliberately small: process management, namespace setup, root filesystem switching, and cgroup limits. No image format, no registry, no networking beyond namespace creation. Those are the parts that make a real runtime large, and none of them would have taught much beyond what the core already did.