Minimal container runtime in C11, built from scratch on Linux primitives. No external libraries: namespaces, chroot and cgroups v2 are driven directly through syscalls and the kernel filesystem interface.
tinydocker [OPTIONS] -- COMMAND
||
\/
argument parser -> container config { hostname, rootfs, cpus, memory }
||
\/
cgroup setup (v2 unified hierarchy)
|-> /sys/fs/cgroup/tinydocker-<pid>/cpu.max
|-> /sys/fs/cgroup/tinydocker-<pid>/memory.max
||
\/
clone(CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWNS)
||
\/
child process
|-> sethostname()
|-> chroot(rootfs) + chdir("/")
|-> mount("proc", "/proc", "proc")
|-> execvp(COMMAND)
The parent creates the cgroup, writes the child PID into cgroup.procs, then waits. The child runs in its own UTS, PID and mount namespaces, so ps inside the container sees a process tree starting at PID 1.
| Namespace | Isolates |
|---|---|
| UTS | Hostname, so the container can set its own without affecting the host |
| PID | Process tree, with the entrypoint as PID 1 |
| Mount | Mount points, so /proc is mounted for the container only |
chroot confines the filesystem view to a minimal rootfs, and /proc is remounted inside so process introspection reflects the container rather than the host.
Limits are enforced through the cgroups v2 unified hierarchy. A dedicated cgroup is created per container, cpu.max and memory.max are written from the CLI flags, and the container PID is moved into it before exec. A process exceeding memory.max is killed by the kernel OOM killer, exactly as it would be under Docker.
make # build to build/bin/tinydocker
sudo ./build/bin/tinydocker -- /bin/sh # defaults: 1 CPU, 512 MB
sudo ./build/bin/tinydocker -h web -c 2 -m 1024 -- /bin/sh| Flag | Description | Default |
|---|---|---|
| `-h, --hostname NAME | Container hostname | container |
-r, --rootfs PATH |
Root filesystem to chroot into | ./rootfs |
-c, --cpus N |
CPU limit | 1 |
-m, --memory SIZE |
Memory limit in MB | 512 |
Requires Linux with cgroups v2 (unified hierarchy) and root privileges. Tested on Ubuntu 22.04 LTS.
- Networking: veth pair, Linux bridge, static IP assignment and NAT through iptables, adding
CLONE_NEWNETto the isolation set. - Volumes:
-v /host:/containerbind mounts. - Image loading: extract a
.tarimage into the rootfs instead of requiring a prebuilt one.