yolobox 0.2.0

Branch-scoped Linux VMs for local development on macOS
yolobox-0.2.0 is not a library.

yolobox

Branch-scoped Linux VMs for local development on macOS. Each repo branch gets its own persistent VM with a writable root disk, a shared git checkout, and a stable network identity.

yolobox base import --name ubuntu --image ./ubuntu-jammy-arm64.raw
yolobox --repo git@github.com:org/repo.git --branch main

You're dropped into a Linux shell with your repo at /workspace, your SSH agent forwarded, and guest services reachable from the host at <project>-<branch>.local (e.g. repo-main.local).

How It Works

  • Import a Linux cloud image once as an immutable base image
  • Each launch clones that base (APFS copy-on-write) into a per-instance root disk
  • cloud-init configures the guest: user account, SSH key, hostname, mounts
  • krunkit boots the VM with virtio-blk (root disk) and virtio-fs (host directories)
  • vmnet-helper gives the guest a real IP on your local network with mDNS (<hostname>.local)

Instances are persistent. Relaunching the same repo+branch reuses the existing root disk and checkout.

Prerequisites

macOS on an APFS volume, plus:

# VM runtime
brew tap slp/krunkit
brew install krunkit

# Networking
curl -fsSL https://raw.githubusercontent.com/nirs/vmnet-helper/main/install.sh | sudo bash

# Image conversion (only if your base image is qcow2)
brew install qemu

You need an SSH key at ~/.ssh/id_ed25519, id_ecdsa, or id_rsa. Create one if you don't have it:

ssh-keygen -t ed25519

Check readiness:

yolobox doctor

For a one-shot setup, use the installer script:

curl -fsSL https://raw.githubusercontent.com/jvanderberg/yolobox/main/scripts/setup.sh | bash -s -- install

Run it locally from a checkout with:

./scripts/setup.sh install

Getting a Base Image

Any Linux image that supports EFI boot, cloud-init, sshd, virtio-blk, and virtio-fs will work. Ubuntu cloud images are a good default:

curl -LO https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-arm64.img
qemu-img convert -f qcow2 -O raw jammy-server-cloudimg-arm64.img ubuntu-jammy-arm64.raw
yolobox base import --name ubuntu --image ./ubuntu-jammy-arm64.raw

Skip the qemu-img step if your download is already a raw image.

Usage

Launching Instances

Launch a VM for a repo branch:

yolobox --repo git@github.com:org/repo.git --branch main

Omit --branch to pick from recent remote branches interactively. Omit --base and yolobox uses the newest imported base image.

Launch a standalone VM (no git checkout):

yolobox --base ubuntu
yolobox --name tools-box --base ubuntu    # with an explicit name

The guest hostname defaults to <project>-<branch> for git-backed instances (e.g. myrepo-main). Unnamed standalone instances get a random petname instead.

Create a new branch:

yolobox --repo git@github.com:org/repo.git --branch feature/x --new-branch
yolobox --repo git@github.com:org/repo.git --branch feature/x --new-branch --from develop

Tune VM resources:

yolobox --repo git@github.com:org/repo.git --branch main --cpus 6 --memory-mib 12288

Sharing Host Directories

Share extra directories into the guest as virtio-fs mounts:

yolobox --repo git@github.com:org/repo.git --branch main \
  --share ~/Downloads:/mnt/downloads \
  --share ~/src/shared-assets:/mnt/assets

Shares are persisted with the instance -- later launches reuse them automatically. If you change the share set on a running VM, yolobox restarts it to apply the new mounts. Clear saved shares with --clear-shares.

AI and Dev Tool Integration

AI integrations are enabled by default. On launch, yolobox will try to share the host config directories for Codex, Claude, and GitHub into the guest when they exist.

Flag What it shares
--with-ai Compatibility flag for the default behavior
--with-claude Require ~/.claude to be shared and export ANTHROPIC_API_KEY
--with-codex Require ~/.codex to be shared
--with-gh Share ~/.config/gh when present and export GH_TOKEN from gh auth token
--no-claude Disable Claude integration for this launch
--no-codex Disable Codex integration for this launch
--no-gh Disable GitHub CLI integration for this launch

These are virtio-fs mounts, so they persist like any other share. yolobox also installs a profile script in the guest that makes claude run with --dangerously-skip-permissions and codex run with --dangerously-bypass-approvals-and-sandbox by default. Use command claude ... or command codex ... if you want the raw CLI behavior in a shell.

Init Scripts

Run a first-boot script inside the guest:

yolobox --base ubuntu --init-script ./scripts/bootstrap-vm.sh

The script runs once as the guest user (with sudo available), and its output goes to /var/log/yolobox-init.log inside the guest. A sample bootstrap script is included at scripts/bootstrap-vm.sh that installs Rust, Node.js, Python tooling, and common dev packages.

Cloud-Init Overrides

yolobox --repo git@github.com:org/repo.git --branch main \
  --cloud-user josh \
  --hostname my-vm \
  --ssh-pubkey ~/.ssh/id_ed25519.pub \
  --ssh-private-key ~/.ssh/id_ed25519

Disable cloud-init entirely with --no-cloud-init if your base image is already configured.

Managing Instances

yolobox list                                            # all instances and their state
yolobox status --repo git@github.com:org/repo.git --branch main
yolobox stop   --repo git@github.com:org/repo.git --branch main   # shut down VM, keep data
yolobox destroy --repo git@github.com:org/repo.git --branch main  # remove everything
yolobox destroy --name tools-box --yes                  # skip confirmation

Managing Base Images

yolobox base list
yolobox base import --name ubuntu-24.04 --image /path/to/ubuntu.img
yolobox base capture --name ubuntu-dev --repo git@github.com:org/repo.git --branch main
yolobox base capture --name ubuntu-dev --instance tools-box

base capture snapshots a running instance's root disk as a new immutable base. Base names can't be overwritten in place -- remove the old one first if you need to reuse the name.

Accessing Guest Services

Services in the guest are reachable via mDNS:

http://myrepo-main.local:3000
http://myrepo-main.local:5173
ssh josh@myrepo-main.local

yolobox does not create localhost port forwards. The guest gets a deterministic static IP on the 192.168.105.0/24 subnet (derived from the instance ID), and avahi-daemon advertises its hostname over mDNS.

Instance Layout

All state lives under ~/.local/state/yolobox (override with YOLOBOX_HOME):

~/.local/state/yolobox/
  base-images/<id>/
    base.img              # read-only base image (APFS clone of import)
    base.env              # metadata
  instances/<id>/
    instance.env          # metadata: base image, ports, shares, env vars
    checkout/             # persistent git working tree
    vm/branch.img         # writable root disk (APFS clone of base)
    cloud-init/seed.iso   # cloud-init seed
    runtime/
      console.log         # VM console output
      krunkit.pid         # process tracking

Branch disks default to a sparse 32 GiB rootfs (override with YOLOBOX_ROOTFS_MIB). The guest partition and filesystem are grown automatically on first boot.

External Launchers

Set YOLOBOX_VM_LAUNCHER to use your own VM launcher instead of the built-in krunkit path. The launcher receives instance metadata as environment variables:

YOLOBOX_INSTANCE, YOLOBOX_REPO, YOLOBOX_BRANCH, YOLOBOX_CHECKOUT, YOLOBOX_BASE_IMAGE, YOLOBOX_BASE_IMAGE_ID, YOLOBOX_ROOTFS, YOLOBOX_ROOTFS_MB, YOLOBOX_CPUS, YOLOBOX_MEMORY_MIB, YOLOBOX_CLOUD_INIT_IMAGE, YOLOBOX_CLOUD_INIT_USER, YOLOBOX_HOSTNAME, YOLOBOX_GUEST_IP, YOLOBOX_GUEST_GATEWAY, YOLOBOX_GUEST_MAC, YOLOBOX_INTERFACE_ID, YOLOBOX_SSH_PRIVATE_KEY, YOLOBOX_PORTS

Use --shell to skip the VM entirely and get a host shell in the checkout directory with the same env vars set.

Building from Source

cargo build
./target/debug/yolobox doctor

While developing, cargo run -- <args> works in place of yolobox <args>.