Skip to content

Provisioning base VMs

An image supplies the kernel and guest filesystem. A base is a snapshot of that image after boot and application warm-up. The current scripts/bake.py runner targets HVF on Apple Silicon and requires Python 3.11+.

Prepare and bake

From the Nether checkout:

zig build -Dtarget=native
./scripts/fetch-guest-image.sh
# The fetch script does not retain its temporary rootfs tree.
if [ ! -d kernels/rootfs ]; then
  mkdir -p kernels/rootfs
  (cd kernels/rootfs && gzip -dc ../initramfs.cpio.gz | cpio -idm)
fi
./tools/build-guest-aarch64-runtimes.sh
export NETHER_ROOT="$(pwd)"
./scripts/bake.py bake examples/base.nether.toml
./scripts/bake.py fork examples/base.snap --name tenant-1

The runtime-image helper requires Docker and adds Python, SQLite, and Node by default. The example starts Python's HTTP server before capture. Runtime-image building and a live bake were not rerun in the 2026-09-06 documentation audit.

The runner defaults to ~/nether/zig-out/bin/nether; NETHER_ROOT changes the checkout root. It recreates its scratch directory before a bake (default /tmp/nether-bake) or named fork (under /tmp/nether-fork). NETHER_WORK changes those work roots. Use dedicated work directories: existing contents there are removed.

A successful fork leaves a VM process running and prints its PID, control/data sockets, and driveable latency. The runner is not a persistent pool supervisor. Use the control protocol or a process manager to shut the VM down.

Consumed recipe fields

Field Current behavior
image.kernel, image.initramfs Paths resolve relative to the recipe and participate in the cache key
resources.ram_mb, resources.cpus Default 512 MiB / 1 vCPU
Top-level run_as Emits the HVF guest-agent user setting; place it before TOML tables
network.egress deny/omitted leaves NIC off; allow enables networking with firewall disabled
files[].host, files[].guest Passes a host-to-guest file transfer command; see restrictions below
warmup[].run Executes a guest command and waits for its reply
warmup[].start Backgrounds a guest command, logging to /tmp/bake-start.log
ready.command Polls a command with an appended success marker
ready.port Polls using the guest shell's /dev/tcp support
snapshot.out Required; relative to the recipe directory
snapshot.compress none or deflate

The runner symlinks the kernel's directory as kernels/; Nether then opens kernels/Image and kernels/initramfs.cpio.gz. Use that exact layout. Selecting a differently named image file in the recipe does not make the runner stage it under those names.

An arbitrary network.egress string is not parsed as a firewall policy: values other than deny enable net=1, and only allow also emits net_open=1. Use explicit Nether config for CIDR rules. Disk and storage fields are described in snapshot storage.

Readiness and transfer limits

When both readiness fields exist, ready.command wins. With neither, the runner warns and sleeps two seconds. A port gate needs shell /dev/tcp support; use a command appropriate to the guest instead. The example uses Python to connect to its server.

Warm-up and transfer replies are printed, but the runner does not uniformly validate every guest exit code. Make readiness test the resulting application state.

__put__ is limited to a regular file of at most 16 MiB, and Nether confines host transfer paths to its launch-directory jail. The runner resolves a host path relative to the recipe but does not copy that file into its scratch jail. A file elsewhere in the checkout is therefore not automatically transferable. Bake application files into the image or stage them through a custom launch flow; the example avoids this unresolved staging seam.

Cache identity and launch settings

A manifest hashes the binary, kernel, initramfs, and recipe bytes. A matching manifest plus output is a cache hit. The contents of separately referenced files and persistent disks are not included. Use bake --force when those change.

Rebuild-dependent layout changes can make an old base unrestorable. Rebake after changing Nether. fork warns on a recorded binary-hash mismatch; it does not reject every different build itself. See versioning.

The fork runner emits only restore and control/data-socket settings; it does not replay the recipe's full host configuration. Persistent disks, networking, and custom policy need a configured fork launch. Guest state already in the snapshot, including its boot-time agent settings, is inherited.

Bakes should contain generic application state. Per-tenant credentials and mutable external resources require explicit ownership. COW RAM does not make a shared disk file or external connection private.

See snapshot storage for compression, content-diff status, and the exact GC behavior.