Firecracker Sandboxes

Firecracker runs each Temps sandbox as its own microVM with its own guest kernel under KVM, instead of as a container sharing the host kernel. It is opt-in, Linux-only, and has to be provisioned on the host before it can be selected.


What the Firecracker backend is

Temps has two sandbox backends:

BackendIsolationAvailability
dockerContainer. Shares the host kernel.Default, always available when Docker is reachable.
firecrackermicroVM. Own guest kernel, KVM-enforced boundary.Only after temps firecracker setup completes on a Linux host with /dev/kvm.

When a sandbox runs on Firecracker, Temps boots a VM named with the temps-fcsandbox- prefix from a pinned guest kernel (vmlinux-6.1.141) and a per-VM ext4 root disk. The guest boot arguments are console=ttyS0 reboot=k panic=1 pci=off init=/sbin/temps-vm-agent, plus an ip= argument when the VM is networked.

There is no SSH and no shared filesystem into the guest. Every operation (exec, file read/write, shutdown) travels over vsock to a guest agent, temps-vm-agent, which runs as PID 1:

  • Guest CID 3, agent port 52, host socket v.sock inside the VM directory
  • Maximum frame size 64 MiB
  • Working directory inside the guest is /workspace
  • Timeouts: 15s to wait for the agent to come up, 300s per RPC, 8s shutdown grace before the VM process is killed

Firecracker pins its own component versions: Firecracker and jailer v1.16.1, downloaded and verified against the release .sha256.txt, and the guest kernel verified against a per-architecture sha256 digest baked into the binary.


Host requirements

Setup runs a preflight stage that checks all of these and refuses to continue if any of them fails.

RequirementDetail
LinuxAny non-Linux host is a hard preflight failure. macOS development machines cannot enable the backend.
x86_64 or aarch64Both have pinned guest-kernel digests. Other architectures are rejected.
Host kernel 4.14 or newerRead from /proc/sys/kernel/osrelease.
CPU virtualization (x86_64)vmx or svm must appear in /proc/cpuinfo flags.
/dev/kvmMust exist and be openable read and write by the user the Temps server runs as.
mkfs.ext4Debian and Ubuntu: apt install e2fsprogs.
ipDebian and Ubuntu: apt install iproute2.
A reachable Docker daemonUsed to build every rootfs. See Docker is still required.
temps-vm-agentThe static musl guest binary, installed to <data_dir>/firecracker/bin/temps-vm-agent. Without it there is no way into a VM.
Root, for the network stage onlyBridge, NAT, sysctl, and the TAP pool. Everything else runs unprivileged.

bridge (from iproute2) is optional. It is used for best-effort per-TAP port isolation and is silently skipped when the tool is absent.

Setup gives targeted remediation for /dev/kvm problems rather than a generic error: sudo modprobe kvm_intel or kvm_amd when the device is missing, sudo usermod -aG kvm <user> plus a re-login when the open is denied. It also detects the stale-session case, where the user is already in the kvm group but the current login predates the change, and suggests sg kvm -c '...'.

Once provisioned, the runtime availability check requires all five of the following to hold, on every check:

  1. state.json records smoke_ok: true
  2. bin/firecracker exists
  3. bin/temps-vm-agent exists
  4. A pinned guest kernel resolves
  5. /dev/kvm opens read and write

Enable it with temps firecracker setup

temps firecracker setup

setup is the only subcommand under temps firecracker. It runs its stages in order:

  1. Preflight. Everything in Host requirements.
  2. Binaries. Downloads firecracker and jailer at the pinned version and verifies both against the release .sha256.txt.
  3. Guest agent. Installs the temps-vm-agent binary into the data directory.
  4. Guest kernel. Downloads vmlinux-6.1.141 and verifies its sha256 digest.
  5. Network (requires root). Creates the temps-fc-br0 bridge, runs ip addr replace <subnet>, enables net.ipv4.ip_forward and persists it to /etc/sysctl.d/99-temps-firecracker.conf, installs iptables MASQUERADE and FORWARD rules, and pre-creates the TAP pool (temps-fc-tap prefix).
  6. Jailer uid range check. Aborts with jailer uid range collision if the reserved uid range overlaps an existing /etc/passwd entry.
  7. Smoke test. Boots a real VM from busybox:stable with 1 vCPU and 128 MiB, with a 60s timeout.

Only a passing smoke test writes smoke_ok: true to <data_dir>/firecracker/state.json, and only that flag makes the backend selectable. If you run setup as a non-root user, the network stage is deferred and setup prints the command to finish it:

sudo temps firecracker setup --network-only

To validate a host without changing anything, use --check. It runs the preflight probes plus a provisioned-state report (binaries, guest kernel, bridge, smoke-test state) and exits non-zero if any check failed, which makes it usable in a provisioning script:

temps firecracker setup --check

To remove the bridge, NAT rules, binaries, kernel, and cached state:

sudo temps firecracker setup --uninstall

Setup flags

  • Name
    --data-dir
    Type
    path
    Description

    Data directory for configuration and runtime files. Also readable from the TEMPS_DATA_DIR environment variable. Optional; resolved the same way the server resolves it.

  • Name
    --check
    Type
    flag
    Description

    Run every probe without mutating the host, print a provisioned-state report, then exit non-zero if anything failed.

  • Name
    --network-only
    Type
    flag
    Description

    Run only the network stage (bridge, NAT, TAP pool). Requires root.

  • Name
    --uninstall
    Type
    flag
    Description

    Remove the bridge, NAT rules, binaries, kernel, and cached state.

  • Name
    --skip-smoke
    Type
    flag
    Description

    Skip the smoke-test VM boot. The backend is explicitly left disabled (smoke_ok: false), so Firecracker will not be selectable afterwards.

  • Name
    --subnet
    Type
    CIDR
    Description

    Gateway address and subnet for the sandbox bridge, in CIDR notation. Default 192.168.222.1/24.

  • Name
    --uid-base
    Type
    u32
    Description

    First uid/gid of the jailer's unprivileged identity range. Default 52000.

  • Name
    --uid-count
    Type
    u32
    Description

    Number of uids/gids reserved for jailed VMs. Default 1000.

  • Name
    --tap-count
    Type
    u32
    Description

    Number of persistent TAP devices to pre-create, one per concurrent networked VM. Default 32. They are created root-owned but user-attachable so the unprivileged server can wire up VMs without privileges.

  • Name
    --tap-user
    Type
    string
    Description

    User allowed to attach the TAP devices. Defaults to $SUDO_USER, then $USER, then root.

  • Name
    --vm-agent-bin
    Type
    path
    Description

    Path to the static musl temps-vm-agent guest binary to install. Defaults to a temps-vm-agent file next to the running executable.

Guest addressing follows from --subnet and the TAP pool: each VM gets the gateway network with 10 + tap_index in the host part, so allocation starts at .10 and leaves room for the gateway. The guest MAC is derived from the guest IP octets as 06:fc:<a>:<b>:<c>:<d>.


Select the backend per sandbox

Send backend: "firecracker" when creating a sandbox. Omit the field to get the host default.

curl https://your-temps-instance.com/api/v1/sandboxes \
  -H "Authorization: Bearer your-temps-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "backend": "firecracker",
    "image": "alpine:3.20",
    "cpu_limit": 2,
    "memory_limit_mb": 1024,
    "disk_size_mb": 4096,
    "timeout_secs": 1800,
    "ports": [3000]
  }'

A successful create returns 201 Created with a sandbox response body. Creating a sandbox requires an authenticated request with either the sandbox-write or the project-write permission.

  • Name
    backend
    Type
    string
    Description

    "docker" (default) or "firecracker". Any other value is rejected. Omit it to use the host default.

  • Name
    image
    Type
    string
    Description

    Container image used to build the root filesystem. This is the field that picks the image; the @vercel/sandbox runtime field is accepted for compatibility and then ignored. Defaults to alpine:3.20.

  • Name
    cpu_limit
    Type
    number
    Description

    Cores. On Firecracker this becomes the vCPU count, rounded up and floored at 1. Default 1 vCPU.

  • Name
    memory_limit_mb
    Type
    number
    Description

    Guest memory. Firecracker default is 512 MiB.

  • Name
    disk_size_mb
    Type
    number
    Description

    Root disk size. Firecracker only; the Docker backend ignores it. Default 1024 MiB. Grow-only, and clamped up to the image's content size when that is larger.

  • Name
    timeout_secs
    Type
    number
    Description

    Idle timeout in seconds. Clamped to the range 60 to 86400; default 3600. The @vercel/sandbox timeout field (milliseconds) is used only when timeout_secs is absent.

  • Name
    env
    Type
    object
    Description

    Extra environment variables baked in at create time. Empty by default.

  • Name
    ports
    Type
    array
    Description

    Ports to expose. Each one becomes a route entry in the create and get responses.

  • Name
    source
    Type
    object
    Description

    Optional code to seed the sandbox with, tagged by type: either {"type":"git", ...} or {"type":"tarball", "url": "..."}. For git, git_connection_id is mutually exclusive with username and password, and credentials embedded in the URL are rejected.

  • Name
    preview_password
    Type
    string
    Description

    8 to 256 characters. Puts every preview URL behind a login form. Never returned; responses only expose the last four characters as a hint.

The host default backend itself is a global setting (sandbox_backend, either "docker" or "firecracker") that is only consulted when the Firecracker backend probes as available. Otherwise Docker is used regardless of the setting.

Three related routes matter on Firecracker specifically:

RoutePurpose
POST /api/v1/sandboxes/{id}/resizeGrow the root disk, 256 MB to 65536 MB. Firecracker only; a non-Firecracker sandbox is rejected with 400.
GET /api/v1/sandboxes/rootfsInventory of the cached Firecracker root filesystems.
POST /api/v1/sandboxes/rootfs/gcGarbage-collect that cache.

To find out whether a host can serve Firecracker before you try, GET /api/settings/sandbox-status returns a firecracker_available boolean (with a per-project variant under the project's agents routes).


When the host cannot run Firecracker

Asking for "firecracker" on a host that has not been provisioned fails the create request. It does not silently fall back to Docker.

The response is 400 Bad Request with content type application/problem+json:

{
  "title": "Validation Error",
  "detail": "backend 'firecracker' is not available on this host"
}

An unrecognized backend string produces the same status with a different detail:

{
  "title": "Validation Error",
  "detail": "unknown backend 'local' (expected \"docker\" or \"firecracker\")"
}

Docker is still required

Firecracker replaces the container runtime, not the image toolchain. In v1 every Firecracker root filesystem is built from a container image using Docker, which is why a dead Docker daemon is a hard preflight failure even on a fully provisioned host.

The pipeline for an image is:

  1. Pull the image and create a container from it.
  2. Export the container filesystem to a temporary tar stream.
  3. Extract it unprivileged. Only regular files, directories, symlinks, and hardlinks are extracted; device nodes are skipped.
  4. Scrub /.dockerenv, /etc/resolv.conf, /etc/hostname, and /etc/hosts.
  5. Inject the guest agent at /sbin/temps-vm-agent.
  6. Build the image with mkfs.ext4 -b 4096 -E lazy_itable_init=0,lazy_journal_init=0 -d over the extracted tree.

The result is cached per image digest and sized to the content size times 1.5 plus 64 MiB. Per-VM copies are made with reflink or sparse copies and grown from there. They are never shrunk.


On-disk layout

Everything lives under <data_dir>/firecracker/:

PathContents
bin/firecracker, bin/jailer, bin/temps-vm-agentPinned binaries plus the guest agent.
kernel/vmlinux-<version>Digest-verified guest kernel.
state.jsonProvisioning state, including the smoke_ok flag that gates availability.
rootfs-cache/<image-digest>.ext4Digest-keyed rootfs cache.
vms/<name>/Per-VM rootfs.ext4, vm.json, fc.pid, v.sock, console.log, env.json.
taps.jsonTAP pool allocation.

Current limitations

This is v1. The following are deliberately deferred, and it is better to plan around them than to discover them at runtime.

The jailer is not used. Setup downloads the jailer binary and validates a uid range for it, but the VMM is launched directly as the server's own user. Isolation is what KVM provides, not a jailer chroot with a dropped uid.

Pause and resume are not snapshots. POST /api/v1/sandboxes/{id}/pause sends a shutdown RPC to the guest agent, waits 8 seconds, then kills the VM process. resume cold-boots a fresh VM from the persisted vm.json and rootfs.ext4. Filesystem state survives; memory and process state do not. No Firecracker snapshot API is involved.

restart is a full guest reboot (stop, then start), not a supervisor-style process restart.

Disk resize is offline. Growing the root disk stops the VM, grows the image, and starts it again, so the guest reboots. Shrinking is rejected with an explicit error.

Network modes. Only "no network" and full network work. Full network means NAT egress that is open to the internet; the only controls in place are iptables DROP rules for the cloud metadata address (169.254.169.254/32), the rest of link-local (169.254.0.0/16), and guest-to-host traffic arriving from the bridge, plus best-effort per-TAP bridge isolation. The "restricted" mode is accepted but fails closed to no network at all, with a warning in the logs, because the egress proxy it depends on has not shipped. It does not silently grant the egress it was asked to restrict.

No mid-run output streaming. Output is delivered after the command finishes. Streaming exec falls back to wrapping the non-streaming path, so every line arrives tagged as stdout and stderr never arrives as a separate stream.

No user switching inside the VM. Running a command as root or as a specific user falls through to a plain exec; there is no uid concept in the microVM path.

One CLI subcommand. temps firecracker setup is all there is. There is no temps firecracker status; --check fills that role.

Partial CLI parity for the backend field. The @temps-sdk/cli package has no --backend flag today, so scripting against the API means setting the field directly. The server binary's own CLI does support it: temps sandbox create --backend firecracker.

Was this page helpful?