Rootfs profiles
A profile is a pre-baked guest rootfs a sandbox can boot from — base, or a language environment like python-3.12 / node-22 / go-1.24. Profiles are built from the official language base images (python:3.12-slim, node:22-bookworm-slim, …), so the toolchain lives exactly where the language expects it: pip, npm, go, venv all behave the way agent code assumes, with no bolted-on tarballs or PATH surprises.
How a profile is built
scripts/build-profile.sh (wrapped by make profile) turns an official image into a Firecracker-bootable ext4:
docker buildfrom the base image (profiles/Dockerfile), injecting only what a microVM guest needs on top of the native toolchain: an init (systemd+systemd-networkd), thecrucible-agentsystemd unit, and crucible's static DNS config.docker exportthe container filesystem to a tar.mkfs.ext4 -dthe tree into an image, inside a singlefakerootsession soroot:rootownership survives without host-side sudo.
The container is never run — Firecracker boots the exported rootfs directly with init=/sbin/init.
Building
Prerequisites: Docker, plus fakeroot and e2fsprogs (mkfs.ext4/debugfs). Then:
make agent # build the guest agent first
make profile PROFILE=python-3.12 # → assets/profiles/python-3.12.ext4
make profile PROFILE=node-22
make profile PROFILE=baseImages are linux/amd64 (Firecracker requires KVM on x86-64). Each language image is a few hundred MB; they are not committed to the repo (assets/ is gitignored).
Available profiles
The set lives in profiles/profiles.env as <profile> <base-image>:
| Profile | Base image |
|---|---|
base |
debian:12-slim |
python-3.12, python-3.13 |
python:3.x-slim-bookworm |
node-20, node-22, node-24 |
node:2x-bookworm-slim |
go-1.23, go-1.24 |
golang:1.x-bookworm |
Adding a version is one line in profiles.env (pin an exact base tag for reproducibility), then make profile PROFILE=<name>. The profile name is whatever a create request selects.
Serving profiles
Point the daemon at a directory of built images:
crucible daemon \
--firecracker-bin /usr/local/bin/firecracker \
--kernel assets/vmlinux \
--rootfs assets/profiles/base.ext4 \
--rootfs-dir assets/profiles \
--jailer-bin /usr/local/bin/jailerThe daemon scans --rootfs-dir at startup: each <name>.ext4 becomes a profile named <name>. --rootfs is the default used when a request names no profile.
Aliases are just symlinks — ln -s node-22.ext4 assets/profiles/node.ext4 gives a node profile pointing at the current LTS. A broken symlink fails at startup rather than at create time.
Selecting a profile
Name it in the create request; omit it for the default:
curl -sS -XPOST localhost:7878/sandboxes \
-H 'Content-Type: application/json' \
-d '{"profile":"python-3.12","memory_mib":1024}'
# → {"id":"sbx_...","profile":"python-3.12",...}An unknown profile returns 400. The chosen profile is echoed back in the sandbox object and applies to cold create; forks inherit their parent snapshot's rootfs, so a snapshot taken from a python-3.12 sandbox forks python-3.12 children. See api.md.
Notes
- Boot validation is yours to run. The build verifies the agent and its enablement symlink are present in the image (
debugfs), but confirming a profile boots means running the daemon and creating a sandbox from it on a KVM host. - Publishing prebuilt images (so users download instead of build) is a distribution step tracked separately; today profiles are build-it-yourself from the pinned recipes.