Skip to main content
A golden is a packed, architecture-specific microVM artifact. With no custom base, the pool uses the pinned official runner source: file-backed pools download the packed artifact, while a backend that builds in the guest can bake the official runner base. A custom base is baked with the runner contract. Each job runs in a disposable VM or copy-on-write fork of that golden.

Select an image

PRELOOP_RUNNER_BASE_IMAGE wins over [golden].base_image. [golden].kind and [golden].dockerfile record how the setup wizard chose the image; the serving path reads base_image. The default packed OCI references are digest-pinned:
Override the OCI reference with PRELOOP_GOLDEN_OCI_REF. To use a release-asset mirror, set PRELOOP_GOLDEN_URL; it replaces the release-asset candidates and skips OCI. The mirror must provide <url>.sha256 or an explicit PRELOOP_GOLDEN_SHA256. Transfers resume from <payload>.partial with HTTP Range requests and are installed only after digest verification. Release lookup is cached for five minutes and prefers stable releases. The pool stores payloads below <PRELOOP_HOME>/vms/, keyed by the sanitized base-image name and environment fingerprint. preloop golden-path --home PATH --base-image REF prints the exact path.

Build a custom golden

Build the Linux guest runner and bake an immutable base image on a host matching the guest architecture:
--runner-bundle must name a directory containing a Linux preloop-runner. --base-image accepts an OCI reference, Docker-save archive, .smolmachine pack, or rootfs path. --storage-gb raises the guest disk ceiling for large snapshots. The published official golden is normally downloaded rather than rebuilt by this command; without a configured custom base, build-golden refuses to invent one. For a digest-pinned custom source, require and verify provenance:
PRELOOP_BASE_IMAGE_IDENTITY_REGEXP changes the keyless Sigstore identity match; PRELOOP_BASE_IMAGE_PUBKEY switches verification to a public key. The base-image verification checks a Cosign signature and SPDX attestation. A configured image is used as-is; add organization-wide packages to that image rather than expecting build-golden to install a package list.

What the bake and guest setup provide

The served pool defaults to the runner account (UID 1001), writable _work, /opt/hostedtoolcache, and the RUNNER_TOOL_CACHE/AGENT_TOOLSDIRECTORY environment entries. A bake attempts to write /etc/preloop-bake.json, checks for a glibc loader, and refreshes apt indexes when apt exists. The contract does not choose language toolchains; use setup actions or a job container. At workload launch, the engine raises hosted-runner limits to a 16 MiB soft stack limit with unlimited hard limit and 65536 soft/hard file descriptors. Per-machine initialization applies: The engine also verifies that the guest hostname resolves to a local address and removes stale foreign mappings. The guest does not expose /dev/kvm: workloads that need nested virtualization must run on a runner outside the microVM pool.

Runner bundle discovery

The engine searches in this order:
  1. PRELOOP_RUNNER_BUNDLE.
  2. The installed release path <prefix>/lib/preloop/runner/<triple>/preloop-runner.
  3. Development and executable-adjacent candidates under the selected Linux target.
The bundle must be a Linux ELF binary even when the host is Linux. install.sh and preloop update install the guest bundle on both Linux and macOS. On a development host, set PRELOOP_RUNNER_BUNDLE explicitly when automatic target discovery cannot find the right architecture. If the bundle is absent, the engine logs that there is no runner pool and jobs fail after the queue grace window. Install the bundle with preloop update or set PRELOOP_RUNNER_BUNDLE to its directory. On the AgentENV backend the bundle is not copied into each golden: the host tree is archived into a content-addressed read-only volume named preloop-runner-<digest>, so a bundle update publishes a new volume instead of requiring a golden rebuild. preloop volumes list shows what is held and the versions each volume was populated from; preloop volumes prune and preloop volumes gc reclaim what nothing needs (the engine also collects superseded volumes on its own). See AgentENV read-only volumes for the retention rules.

Version pins

versions.toml controls the runner, SmolVM, and Node external pins: official-image.toml pins the upstream runner-image sources:
That file also records the hosted runtime limits and sysctl values shown above. Changing a compiled pin requires rebuilding and replacing the preloop executable; it does not change an already-installed binary. smolvm_min_version is a capability floor rather than a tracked release: it moves when a SmolVM release becomes the first to satisfy a runtime invariant Preloop depends on (fork freeze-source, packed ownership, the x86_64 virtio device budget). preloop update --ensure-runtime accepts and upgrades from that floor.

The virtio device budget (IRQ lines)

A microVM’s device count is capped by libkrun’s MMIO IRQ allocation: one line per virtio device. Measured on x86_64 Linux at SmolVM 1.30.1, the allocation is 19 lines (IRQ 5-23) since SmolVM 1.23.0 and was 11 before that; an image machine created with --storage/--overlay starts with 9 devices, and each --volume, --mount-socket or extra --disk costs one. The 20th device fails the boot with krun_start_enter returned: -22 (EINVAL) and no more IRQs are available — the same EINVAL shape as a held or corrupt disk image, which is why the engine classifies it (VmError::IrqExhausted) and reports the machine’s declared inventory and the ceilings. The arm64/macOS ceiling has not been measured.

Troubleshooting