All projects

Personal project

Wharf

A Linux container runtime in Python with namespaces, an overlay root filesystem, cgroup v2 limits and a Docker-style CLI.

  • Python
  • Linux
  • namespaces
  • cgroups

wharf

$ wharf pull tools:latest --host /bin/sh /bin/echo /bin/sleep /bin/ls
tools:latest
$ wharf run tools:latest /bin/echo hello from a container
hello from a container
$ wharf run -d --name nap --memory 64m --cpu 0.5 --pids 64 tools:latest /bin/sleep 300
0cb542f06c8a
$ wharf ps
id            name  image         status   pid    command
0cb542f06c8a  nap   tools:latest  running  54372  /bin/sleep 300
$ wharf exec nap /bin/ls /proc/1/ns
cgroup  ipc  mnt  net  pid  pid_for_children  time  time_for_children  user  uts
$ wharf stop nap
0cb542f06c8a exited
A real session. The CLI module is still named rubberdocker in the repo, shown here as wharf for readability, and the ps and exec output is condensed.

Problem

Containers are often explained as lightweight VMs, but they're ordinary Linux processes with a restricted view of the system. I wanted to understand that by building the whole path myself, from unshare to a running process in its own root filesystem.

What I built

Wharf gives a process its own user, mount, UTS, PID, IPC and network namespaces, then pivots it onto an overlay root filesystem. It stores images locally and enforces cgroup v2 memory, CPU and pids limits.

A Docker-style CLI covers pull, run, ps, exec, logs, stop and more, and a small control page lists what is running.

How

  1. CLIrun, ps, exec, logs, stop
  2. SupervisorUnshares a user namespace and writes the id map
  3. NamespacesMount, UTS, PID, IPC, network
    cgroup v2memory.max, cpu.max, pids.max
  4. Overlay rootfsOrders lowerdirs, applies OCI whiteouts
  5. pivot_rootMounts, pivots and detaches the old root
  6. Container processsetns lets exec join it later

The supervisor unshares a user namespace first and maps container root onto the calling user, so run needs no root when unprivileged user namespaces are enabled.

pivot_root is called directly as a syscall, exec enters a running container with setns, and layers resolve in overlay order with OCI whiteouts applied.

Results

  • It runs real containers end to end: detached workloads with memory, CPU and pids limits, exec into a live container, and clean stops.
  • The full ps and exec flow above was run from a fresh clone on WSL2.
  • The limits are explicit: there is no registry client, daemon, seccomp profile, capability bounding set or veth bridge. This is a learning runtime, not Docker.

What I learned

  • Order is everything. The id map, mounts, pivot and cgroup writes each depend on what has already happened, and getting one out of order fails in confusing ways.
  • Designing for rootless use forced me to learn which privileges a container really needs.
Next projectMarconi