bladerunner is a standalone Incus VM runner for macOS built directly on Apple Virtualization.framework via github.com/Code-Hex/vz/v3.
It is designed to provide the core behavior of a colima --runtime incus setup without Lima/Colima orchestration overhead:
- Architecture-aware defaults (
arm64andamd64) using Debian 13 (trixie) genericcloud images. Ubuntu and other cloud images remain reachable via--image-urlorBLADERUNNER_BASE_IMAGE_URL. - Incus daemon bootstrapped inside the guest via cloud-init.
- Localhost-accessible SSH and Incus HTTPS endpoints via virtio-vsock port forwarding.
- Incus web dashboard availability through the forwarded API endpoint.
- Optional bridged networking (for transparent L2 presence) when signed with
com.apple.vm.networking. - Startup report generation with VM, network, and access details.
- Optional GUI console window (
StartGraphicApplication) with serial output logged to file. - Rotating structured logs with stage-level observability and live progress indicators for long-running tasks.
- No OpenID setup by default.
-
Apple Silicon Mac (M1/M2/M3/M4) - Intel Macs not supported
-
macOS 13+ (Ventura or later)
-
Xcode Command Line Tools (includes codesign utility)
xcode-select --install
-
Binary must be code-signed with Virtualization entitlement (automatic with Homebrew)
-
For bridged networking, additional VM networking entitlement required
brew install stuffbucket/tap/bladerunnerThe binary is automatically signed with required entitlements during installation.
Requires Xcode Command Line Tools:
xcode-select --installBuild and sign:
make build
make signOr manually:
go build -o bin/br ./cmd/bladerunner
codesign --entitlements vz.entitlements -s - bin/brDefault (shared network + localhost forwarding):
runner startWith GUI console window:
runner start --guiBridged network on en0:
runner start --network-mode bridged --bridge-interface en0Custom image path (raw disk image):
runner start --image-path /path/to/base.rawCustom log file path:
runner start --log-path /tmp/bladerunner.logOptional log level. Accepts debug, info, warn (alias warning), or
error (case-insensitive). Unknown or unset values default to info:
BLADERUNNER_LOG_LEVEL=debug runner startAfter startup, the tool prints a report and writes JSON report data to:
~/.local/state/bladerunner/startup-report.json
Key defaults:
- Incus API/UI endpoint:
https://127.0.0.1:18443 - SSH endpoint:
127.0.0.1:6022 - Dashboard URL:
https://127.0.0.1:18443/ui/ - Log file:
~/.local/state/bladerunner/bladerunner.log(rotated with compression)
Example SSH:
ssh -p 6022 incus@127.0.0.1Example REST call:
curl --cert ~/.local/state/bladerunner/client.crt --key ~/.local/state/bladerunner/client.key -k https://127.0.0.1:18443/1.0A disk is a .disk JSON manifest that bundles an image identity, VM sizing
recommendations, and a boot mode (headless or GUI) — think of it as a labeled
floppy you slide in and power on. Booting a disk materializes its image, applies
sizing, and runs the VM in an isolated per-disk state slot, restoring saved guest
RAM when present. A disk that pins its image SHA-256 (e.g. after br disk bake) is materialized once into a shared content-addressed cache and reused
across slots; the digest is verified before use.
runner disks # list the shelf (builtins + your disks) and attached cartridges
runner boot <name|url|path> # power on a disk (restores saved RAM if present)
runner eject # cleanly power off the running VM (ACPI shutdown)
runner disk new <name> # scaffold a new user disk manifest
runner disk bake <name> # build its qcow2 and record the image SHA-256
runner disk pack <name> # pack a disk into an AirDrop-able cartridgebr eject performs a clean ACPI shutdown (it loops the power button and
waits for the guest to power off, then forces the stop after --timeout). For a
same-host RAM resume use br save + br restore instead — eject is a
clean cold-stop by design.
Two disks ship built in:
incus— headless Incus host using the pre-baked bladerunner guest image (theguest-image-latestrelease; this is the classicbr startsetup).debian-trixie-gui— a Debian Trixie desktop that opens in a VZ window.
br boot <name> resolves a catalog disk; br boot <url> boots a one-off
headless image by URL; br boot ./my.disk boots a manifest file directly.
--cpus/--memory/--disk override the manifest's sizing, and
--gui/--headless override its boot mode. --no-restore forces a cold boot.
Layout:
- User disks:
~/.config/bladerunner/disks/*.disk - Per-disk state slots:
~/.local/state/bladerunner/disks/<name>/(each slot has its owndisk.raw,saved-state.bin, console log, EFI vars, and cloud-init) - Shared image cache (SHA-256-pinned disks only):
~/.local/state/bladerunner/cache/images/<sha256>.raw
br disk bake shells out to scripts/build-guest-image.sh and is a
host-side developer action: it requires bash, qemu-img, and the script's
build dependencies (libguestfs-tools, likely sudo). Builtin disks are
read-only — fork one with br disk new <name> --from <builtin> first.
A cartridge is a single, self-contained, AirDrop-able macOS disk image holding
a complete bootable VM: the disk manifest, the root disk, EFI + cloud-init state,
and a read-write host↔guest share folder. Because br eject always
powers the guest off cleanly via ACPI, a cartridge is always in a consistent
cold-boot state — so you can AirDrop the file to any Mac running bladerunner and
br boot <file> just works (a clean cold boot). The clean-shutdown invariant
is what makes AirDrop safe: no dirty filesystem, no host-bound RAM snapshot.
The honest tradeoff: the disk is portable (cold-boot on any Mac), while same-host RAM resume is intentionally out of scope — we shut down cleanly instead of carrying a machine-bound memory image around.
runner disk pack incus # build ./incus.sparseimage (runnable)
runner disk pack incus --ship # also build ./incus.dmg (compressed AirDrop artifact)
runner boot ./incus.sparseimage # mount + cold-boot the cartridge
runner boot ./incus.dmg # materialize a working copy, then boot it
runner eject # clean ACPI shutdown, then detach the cartridge
runner disks # also lists attached cartridges (booted/ejected)br disk pack <name> resolves a catalog/user disk, creates an APFS sparse
image sized to the disk plus headroom (override with --size), attaches it, writes
disk.json, materializes the bootable root.img (via the same image cache /
qemu-img path boot uses), and creates state/ and share/. --out overrides
the output path; --ship additionally produces a compressed read-only .dmg
(the AirDrop artifact). --arch selects the root image's architecture.
br boot <cartridge> mounts the image privately at
~/.local/state/bladerunner/mnt/<name>/, roots the VM inside it
(root.img, state under state/, the RW share at share/), and owns the
mount — detaching it on exit. A .dmg is first converted to a working
.sparseimage so the shipped read-only artifact stays pristine.
The RW share is exposed to the guest over VirtioFS (tag bladerunner-share)
and mounted at /mnt/share by a generated systemd .mount unit (with an fstab
fallback). VirtioFS maps host files to the guest's mounting context (root), so
the bootstrap chowns /mnt/share to the SSH user — drop files in share/ on
the host and read/write them at /mnt/share in the guest, and vice versa.
Cartridge layout (at the mountpoint):
disk.json the Manifest (image source is THIS cartridge: root.img)
root.img the bootable raw disk (sparse on APFS)
state/efi-vars.bin EFI variable store
state/cloud-init/ cloud-init seed
share/ RW host↔guest VirtioFS folder
Cartridges require macOS (they are backed by hdiutil + APFS sparse images);
packing also needs qemu-img.
- The default base image is the Debian 13 (trixie) genericcloud qcow2 (
incusandincus-clientship in trixie main, so no third-party apt repos are needed). Override with--image-urlorBLADERUNNER_BASE_IMAGE_URLto use Ubuntu 24.04 or another distribution. - The base image can be raw or qcow2 format. qcow2 images are automatically converted to raw via
qemu-img. - First boot can take several minutes while cloud-init installs and configures Incus.
- A pre-baked bladerunner guest image (Debian Trixie + Incus +
br-agent, built byscripts/build-guest-image.shand published via thebuild-guest-imageworkflow) is the future default. While that release pipeline is bootstrapping it is opt-in: setUseHostedGuestImage(or pass--image-urlwith the GitHub Release URL) to use it. Onceguest-image-latestis published the default will flip. - Downloaded base images are SHA-256 verified against a sidecar
.sha256file. The check is strict for upstream Debian URLs and tolerant of a missing sidecar for GitHub Release URLs during the bootstrap window. br statussurfaces the pre-baked image build date from/etc/bladerunner-image-versionwhen present.- GUI output is handled by VZ graphics window; serial console is logged at
console.log. - Extended operations (download, VM readiness, Incus readiness) show live progress indicators in terminal.