Rootless · Daemonless · Content-addressed
Containers you can trace back to every file.
Zigbox is a container runtime, image store and builder written in Zig. It runs the OCI images you already use, with provenance built in.
Design principles
Docker, designed as if it started today
No daemon, no root
Every command is an ordinary process you own. Privilege comes only from user namespaces.
Files, not layers
Images are Merkle trees of BLAKE3-hashed files. Identical files are stored once across every image.
Builds that cannot lie
Steps run sandboxed, offline by default, cached on input content. Same inputs, same digest, on any machine.
Secure by default
Capabilities denied, seccomp on, no network unless granted. Refusals say what was refused and why.
Signed by construction
Signatures cover tree digests, so a host can refuse anything it cannot trace to a trusted key.
Thirty seconds to run
zbx pull alpine && zbx run alpine sh stays as easy as it was in 2013.
Architecture
What is being built, layer by layer
One static Zig binary: no daemon, no libc, no third-party code. Every layer but the last is ordinary user-space code; the privilege it needs comes from kernel features an unprivileged user may already use.
Command line
Every command has a versioned JSON result (--output json, zbx schema) and every failure is a registered diagnostic with a cause and a fix (zbx explain). Nothing prompts. Exit codes never overlap a container's.
Library
The same code as a Zig module, shaped to the runtime contract Zigcube's node agent drives: start, stop, list, re-adopt, events. Each caller owns its memory; nothing is global.
Isolation
clone3 into user, mount, PID, UTS and IPC namespaces; the full subuid map via newuidmap; setup as an explicit plan of steps, then pivot_root.
Limits and hardening
A transient systemd user scope per container, asked for over D-Bus by a wire client written in Zig. A capability allowlist, no_new_privs, and a checked-in seccomp allowlist compiled to BPF at build time.
Content-addressed store
Files are objects named by BLAKE3 over content and metadata; directories are canonical trees, so a root digest names a whole filesystem. Runs use a hardlink farm as the overlay's lower layer.
Pull
An OCI registry client: anonymous token flow, manifest and index parsing, platform selection, std's TLS over sockets with deadlines. Layers are flattened into one store tree.
Every system call goes through one vtable
The real host makes raw Linux syscalls; a seeded simulator stands in for it in tests, with fault injection at every step: a failed mount, a full disk, a bus that refuses, a crash between two writes.
Kernel features, used unprivileged
User namespaces, cgroup v2 delegated by the user's systemd, overlayfs with userxattr, seccomp filters, flock. zbx doctor checks each one and says how to turn it on.
The life of zbx run <digest>
Resolve. A digest, or a unique 12-hex prefix, names a tree in the store. An unknown or ambiguous one is refused before anything starts.
Materialize. In a child inside the user's namespace, the tree becomes a directory of hardlinks to its objects, built once, atomically, and cached.
Scope. zbx asks the systemd user manager for a transient scope with the memory, CPU and PID limits, and puts a small keeper process in it, so the cgroup's counters outlive the container long enough to be read.
Namespaces. A single-threaded child is cloned into new namespaces; the parent writes its id maps. The child mounts an overlay (the farm below, a per-container upper layer above),
/proc, a minimal/devand tmpfs, then pivots into it.Harden. Capabilities drop to the allowlist,
no_new_privsis set, and the seccomp filter goes on last, just beforeexecve.Wait. The parent forwards signals by the PID 1 rules, enforces
--timeout, then readsmemory.eventsandpids.events, so an out-of-memory kill or a fork bomb is reported in plain words.Record. A versioned run record keeps the exit and its reason;
zbx commitcan later turn the upper layer into a new tree.
How it is checked
Simulation. Unit and snapshot tests run against the simulated host, faults included.
Seeded fuzzers. 25 of them, from the image-reference parser to D-Bus messages, seccomp and gc, each replayable from its seed.
Golden diagnostics. Every code is rendered in text, verbose and JSON and compared with checked-in files; no output may show a stack trace or a raw error name.
Real VMs. Each milestone's demo runs start to finish in an Ubuntu 24.04 guest under KVM on every pull request, and every earlier demo with it.
Upgrades. The new binary reads state the previous release wrote.
Mechanical rules.
zig build tidy: line and function length, assertion density, banned patterns, and no test file left unrun.
Coming next
Roadmap and demos
Every milestone ends in a live demo
A milestone counts as done only when its demo runs start to finish on a clean VM. Planned output is marked until the milestone ships.
Documentation
Docs grow with the code
Each page goes live with the milestone that makes it true.
The project
Open, neutral, staying that way
Apache 2.0, forever
A written pledge never to relicense. Contributions use a DCO sign-off, not a CLA.
Decided in public
Design changes go through public RFCs. The path to a neutral foundation is published from day one.
Built to extend
Stable seams for isolation backends, networking, state, policy and registries.