Zigbox Pre-alpha

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.

zbx · example session✓ recorded in CI · M4

    

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.

zbx

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.

zigbox module

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.

runtime

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

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.

store

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.

registry

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.

host interface

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.

Linux

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>

  1. 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.

  2. Materialize. In a child inside the user's namespace, the tree becomes a directory of hardlinks to its objects, built once, atomically, and cached.

  3. 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.

  4. 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 /dev and tmpfs, then pivots into it.

  5. Harden. Capabilities drop to the allowlist, no_new_privs is set, and the seccomp filter goes on last, just before execve.

  6. Wait. The parent forwards signals by the PID 1 rules, enforces --timeout, then reads memory.events and pids.events, so an out-of-memory kill or a fork bomb is reported in plain words.

  7. Record. A versioned run record keeps the exit and its reason; zbx commit can later turn the upper layer into a new tree.

How it is checked

  1. Simulation. Unit and snapshot tests run against the simulated host, faults included.

  2. Seeded fuzzers. 25 of them, from the image-reference parser to D-Bus messages, seccomp and gc, each replayable from its seed.

  3. 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.

  4. 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.

  5. Upgrades. The new binary reads state the previous release wrote.

  6. 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.