Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Disposable browser

ignition can run a throwaway Firefox ESR in a microVM where every write lands in guest RAM, never touches the disk, and a single hotkey resets the session back to a warm homepage — without reloading the kernel or replaying the overlay boot path. cage fullscreens the single Firefox window (so it fills the macOS window), but Firefox keeps its normal toolbar and address bar, so you can navigate anywhere.

What it is

Each browser session is an independent clone of a pre-warmed snapshot. The guest boots once (the “cold boot”), Firefox opens on the homepage, and that moment is frozen as the browser-base snapshot. From then on every session is a sub-second restore: the kernel and overlay setup are already baked in. Closing the window tears the clone down. The base snapshot is never mutated.

Fan-out is first-class: disposable-browser.sh -n N starts N independent clones in parallel, each with its own macOS window, its own copy-on-write instance directory, and (under --net) its own MAC address and DHCP lease.

The overlay-root model

The browser rootfs is designed to keep the backing ext4 image read-only throughout the life of every session. On the cold boot, init=/sbin/overlay-init runs before the normal init: it mounts the ext4 device read-only as the lower layer of an overlay filesystem, places a tmpfs as the upper layer, and switch_roots into the merged view. /tmp, the browser profile directory, and any download paths all live in the tmpfs upper layer.

The consequence is that every write the guest makes — browser cache, cookies, history, tab state — lives in guest RAM and only in guest RAM. The ext4 image is never written.

This also means the warm-base snapshot needs no filesystem sync first: there are no dirty disk pages to flush (the disk is read-only), and the mutable filesystem state lives entirely in the tmpfs upper layer, which the RAM snapshot captures atomically once the vCPUs are parked. The read-only lower is shared unchanged.

This is what makes Ctrl-A r safe. The interactive reset-to-checkpoint mechanism rolls back guest RAM, vCPU registers, GIC state, and virtio-device state to a saved point. For that rollback to be correct, the disk must not have diverged between the checkpoint and the reset. The overlay root guarantees this invariant: there is nothing to diverge. As the snapshot-restore page puts it, the intended usage “mounts the rootfs read-only and places all writable state on a tmpfs overlay that lives in guest RAM” — that is exactly the arrangement overlay-init establishes.

When boot --restore <dir> starts, the restored snapshot is automatically installed as the initial reset point, so Ctrl-A r works from the first keystroke without needing a prior Ctrl-A c.

Build rootfs-browser.ext4

The browser rootfs is built by kimage/build/build-rootfs-browser.sh. See Building guest assets for the full scp/ssh/scp workflow. The short version:

cd kimage
scp build/build-rootfs-browser.sh build/devmem.c artemis2:~/kbuild/
ssh artemis2 'cd ~/kbuild && chmod +x build-rootfs-browser.sh && HOMEPAGE=https://duckduckgo.com ./build-rootfs-browser.sh'
scp artemis2:'~/kbuild/out/rootfs-browser.ext4' out/rootfs-browser.ext4

The HOMEPAGE build argument sets the URL Firefox opens on first paint. The rootfs ships overlay-init at /sbin/overlay-init, which the cold boot activates via --append "ro init=/sbin/overlay-init".

Create the warm-base snapshot

This is a one-time step. After the warm base exists, sessions restore from it instead of cold-booting.

sudo scripts/make-browser-base.sh

The script cold-boots the browser rootfs with --gui --net --mem 2048 and init=/sbin/overlay-init, watches the serial console for the BROWSER_READY signal that the guest emits when Firefox has painted the homepage, sends Ctrl-A s to snapshot the live guest as browser-base, waits for the snapshot write to complete, then exits. No manual timing is required.

An optional snapshot name can be passed as the first argument:

sudo scripts/make-browser-base.sh my-base

Manual flow

If you prefer to watch the boot yourself and choose when to snapshot:

sudo target/debug/boot --gui --net --smp 2 --mem 2048 --name browser-base \
     --append "ro init=/sbin/overlay-init" kimage/out/Image kimage/out/rootfs-browser.ext4

Pass --name browser-base so the snapshot you take is written under that name (the name disposable-browser.sh restores by default). Wait for the Firefox window to paint the homepage (the guest prints BROWSER_READY on the serial console and the llvmpipe software renderer presents the first frame in the macOS window). Once it looks right, press Ctrl-A s to write the snapshot, then Ctrl-A x to quit. (Ctrl-A s writes immediately under --name; there is no name prompt. Without --name the snapshot gets an auto-generated name, which disposable-browser.sh will not find.)

The cold boot passes --append "ro init=/sbin/overlay-init" to hand control to the overlay setup before normal init. Restore does not reload the kernel or re-run the overlay pivot; it resumes from the frozen moment. (No --track-dirty: the GUI reset is a relaunch, not an in-place rollback, so dirty tracking would only add write-protect-fault overhead with no benefit.)

Run a disposable session

scripts/disposable-browser.sh

This restores one clone of browser-base: a GUI window opens with Firefox at the homepage. Under the hood it runs:

target/debug/boot --gui --net --mem 2048 --restore browser-base

--net is included by default; because vmnet shared mode requires elevated privileges, run under sudo when you want networking:

sudo scripts/disposable-browser.sh

Fan-out: N independent sessions

sudo scripts/disposable-browser.sh -n 3

This launches 3 clones in parallel, each with its own macOS window. Under --net each clone gets a distinct MAC address and its own DHCP lease — the browser rootfs carries the same netwatch carrier-poller as the GUI rootfs, which rebinds virtio-net on restore and re-runs DHCP. Without --net (no sudo) the clones are isolated but share the host network stack.

The base snapshot is never written; each clone’s copy-on-write instance directory is private and keyed by the clone’s pid. Closing a clone’s window tears down only that guest. If the script is killed with Ctrl-C it cleans up all child processes.

A non-default base name or additional boot flags can be passed after the clone count:

sudo scripts/disposable-browser.sh -n 2 my-base --store /data/vmstore

Reset a session

With the browser window focused, press Ctrl+Alt+R to reset the clone back to the warm homepage, Ctrl+Alt+S to write a disk snapshot, and Ctrl+Alt+X to close the window. (These are window hotkeys, intercepted before the keystroke reaches the guest; disposable-browser.sh runs each clone backgrounded, so the serial Ctrl-A chords never reach it.)

Ctrl+Alt+R is a cold reset (relaunch): the clone process exits with a sentinel code and disposable-browser.sh re---restores it from the snapshot — the window blinks and reopens at the warm homepage in roughly the cold-restore time (~hundreds of ms; see the latency benchmark). Everything from the session evaporates because the restored clone starts from the immutable warm-base again.

Why relaunch rather than roll back in place: an in-place rollback of a live, actively-rendering GUI guest cannot reconcile the running GIC and virtio devices (net, vtimer, and especially the virtio-gpu fence pipeline) with the rolled-back guest state — hv_gic_set_state is create-time-only on HVF, so in-flight interrupt state wedges the display/network under load. A fresh --restore has none of that (it builds clean device instances and the guest re-initialises), so it is the robust reset for the GUI. The in-place reset (Ctrl-A r on a serial console) is retained for headless guests where it works.

Memory and resource footprint

--mem 2048 (2 GiB) is the default for both make-browser-base.sh and disposable-browser.sh — 1 GiB OOMs Firefox once the tmpfs overlay (profile, cache, /tmp) fills under real browsing. For N clones the RAM cost is approximately N GiB of guest-visible address space, though Apple Silicon memory compression and the CoW instance directories mean the actual resident footprint is lower in practice. The rootfs-browser.ext4 disk image is shared read-only across all clones — only the per-clone tmpfs upper layer (in guest RAM) diverges.

The warm-base is created with --smp 2 (Firefox is happier with more than one core). The vCPU count is baked into the snapshot, so every restored clone gets those 2 cores automatically — disposable-browser.sh does not pass --smp because restore inherits the count from the snapshot (like --mem). Re-create the warm-base with a different --smp value to change it.