A minimal Linux container runtime, written in Go.
box is the CLI front-end for the Boxes runtime. It follows the OCI-style
lifecycle (create → start → kill → delete) and is intended as a
learning-oriented, low-level container runtime in the spirit of runc /
youki.
Status: early development — APIs, on-disk state layout, and CLI flags are unstable and may change without notice.
- Features
- Architecture
- Requirements
- Installation
- Quick Start
- Commands
- Configuration
- Development
- Roadmap
- License
- OCI-compatible container lifecycle (
create,start,kill,delete,state) - Linux namespace isolation via cloneflags (PID, mount, UTS, IPC, network, cgroup, time)
- OCI runtime spec (
config.json) consumption - OCI lifecycle hooks (prestart, createRuntime, createContainer, startContainer, poststart, poststop)
- Reexec-based container init (
/proc/self/exere-exec with namespace cloneflags) - Unix socket IPC between runtime and container process
- Structured JSON file logging with debug toggle
- On-disk state persistence (
state.jsonunder XDG dirs) - Signal handling (by number or name, e.g.
SIGTERM,9) - Image rootfs handling (bring-your-own rootfs)
- Mount setup (pivot_root, bind mounts) — namespace created but rootfs not mounted
- User namespace UID/GID mapping (rootless containers)
- Container environment isolation (currently inherits host env)
- cgroups v2 resource limits (CPU, memory, pids, IO)
- Networking (veth + bridge, then CNI)
┌──────────────┐ ┌──────────────────────┐ ┌──────────────────┐
│ box (CLI) │ ───► │ internal/operations │ ───► │ internal/ │
│ cmd/cli │ │ lifecycle handlers │ │ container │
└──────────────┘ └──────────────────────┘ └────────┬─────────┘
│
┌──────────────────────┐ ┌────────▼─────────┐
│ internal/hooks │ │ Linux kernel │
│ OCI hook execution │ │ ns / cloneflags │
└──────────────────────┘ └──────────────────┘
Container creation uses a reexec pattern: box create forks via
/proc/self/exe reexec <id> with cloneflags that set up Linux namespaces. The
child signals "ready" over a unix socket. On box start, the parent tells the
child to proceed, and the child syscall.Exec()s the user process.
cmd/cli/— Cobra-based CLI entrypoint.internal/cli/— subcommand definitions and flag wiring.internal/operations/— orchestration layer for each lifecycle verb.internal/container/— coreContainertype, state machine, reexec, socket IPC.internal/hooks/— OCI lifecycle hook execution.internal/filesystem/— XDG directory resolution for state/runtime paths.internal/logger/— structured JSON file logging viaslog.internal/errs/— deferred-close error utilities.internal/assert/— custom test assertion library (no external test deps).
- Linux kernel ≥ 5.10 (cgroups v2, user namespaces)
- Go ≥ 1.26.3
- A rootfs directory (e.g. extracted from
docker exportor a distro bootstrap) - For rootless mode:
newuidmap/newgidmapand/etc/subuid+/etc/subgidentries
From source:
git clone https://github.com/michael-duren/boxes.git
cd boxes
make build # produces ./bin/box
make install # installs to $HOME/.local/bin/boxPREFIX can be overridden, e.g. make install PREFIX=/usr/local.
# 1. Prepare a rootfs
mkdir -p /tmp/mybox/rootfs
# ...extract a base image tarball into rootfs/...
# 2. Create the container
box create mybox --bundle /tmp/mybox
# 3. Start it
box start mybox
# 4. Inspect its state
box state mybox
# 5. Stop and clean up
box kill mybox
box delete mybox| Command | Description |
|---|---|
box create <id> |
Create a container from a bundle (does not run) |
box start <id> |
Start the process inside a created container |
box state <id> |
Print the current state of a container |
box kill <id> |
Send a signal to the container's init process |
box delete <id> |
Remove a stopped container and its state |
Run box <command> --help for full flag documentation.
Containers are described by an OCI runtime config.json inside the bundle
directory passed to box create. A minimal example:
{
"ociVersion": "1.0.2",
"process": {
"args": ["/bin/sh"],
"cwd": "/"
},
"root": { "path": "rootfs" }
}State for running containers is persisted under
$XDG_STATE_HOME/boxes/<container-id>/state.json, with runtime sockets at
$XDG_RUNTIME_DIR/boxes/<container-id>/.
Boxes targets Linux. If developing on macOS (or another non-Linux OS), the
project uses direnv to automatically set GOOS=linux
and GOARCH=amd64 so that gopls and other Go tools resolve Linux-only
symbols (e.g. syscall.CLONE_*, unix.SIGKILL).
brew install direnv # if not already installed
echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc # or your shell rc
direnv allow # approve the project .envrcThese variables are scoped to this project directory and do not affect other Go projects.
make build # compile to ./bin/box
make run ARGS="state mybox"
make test # go test ./...
make vet
make fmt
make lint # requires golangci-lint
make tidy
make cleanProject layout:
.
├── cmd/cli/ # CLI entrypoint (Cobra)
├── internal/
│ ├── cli/ # Subcommand definitions + flag wiring
│ ├── operations/ # Lifecycle operation orchestration
│ ├── container/ # Core Container type, state machine, reexec
│ ├── hooks/ # OCI lifecycle hook execution
│ ├── filesystem/ # XDG directory resolution
│ ├── logger/ # Structured JSON file logging (slog)
│ ├── errs/ # Error utilities
│ └── assert/ # Custom test assertion library
├── alpinefs/ # Alpine rootfs bundle + OCI config.json
├── scripts/ # Helper scripts (alpine rootfs, OCI validation, Docker)
├── docs/ # OCI validation research notes
├── slides/ # Slidev presentation
├── Makefile
└── go.mod
box is measured against the upstream
opencontainers/runtime-tools
validation suite. See docs/oci-validation.md for the
CLI contract, suite mechanics, and version pin. To drive one validation test
against the locally-built box (expected to fail during development — needs
root + user namespaces):
./scripts/oci-validation.sh # runs validation/default
TEST=state ./scripts/oci-validation.sh # run a specific test- CLI with OCI lifecycle verbs (
create,start,kill,delete,state) - Reexec-based init process in new namespaces
- Parse and honor OCI
config.json - OCI lifecycle hook execution
- Unix socket IPC (init sock + container sock)
- Persisted container state under XDG dirs
- Mount setup (pivot_root, rootfs, bind mounts)
- Container environment and hostname configuration
- cgroups v2 resource limits
- Rootless support via user namespaces and
newuidmap -
--pid-fileand--console-socketflags (OCI validation) - Networking (veth + bridge, then CNI)
-
execinto a running container - Integration / acceptance tests with a real rootfs
See LICENSE.
