Apple Virtualization (apple_virt)
The Apple Virtualization backend boots full Linux VMs on macOS via
Apple's Virtualization.framework directly (through our avfbind Swift
FFI), with no container CLI or daemon required. It's the default
backend for heyvm wt on macOS.
Requirements
- macOS 13+ on Apple silicon
- Docker Desktop (only for building custom images and for
heyvm wt --deploy --publish-image)
How it works
- The heyvm binary itself owns the VM. No separate daemon.
- Default mode (
heyvm wt <branch>): the VM lives inside the CLI process. When you exit the shell, the VM shuts down. Matches Apple's sample-app lifecycle. - Detached mode (
heyvm wt <branch> --detach): the CLI fork+setsid spawns a smallheyvm __sandbox_owner <id>child that owns the VM. Each sandbox has its own owner; the CLI can exit freely. The owner writes~/.heyo/sandboxes/<id>/owner.jsonwith its pid + the guest's SSH IP. Other CLIs read that file to reattach. - Data plane is SSH: after boot, heyvm probes the guest's eth0 IP via
one serial-console command, installs a managed ed25519 pubkey, and
uses
ssh root@<ip>for every subsequent exec/shell.
Cold boot to SSH-ready: ~10 seconds on M1/M2.
Installation
-
Build and sign heyvm
The heyvm binary needs the
com.apple.security.hypervisor+com.apple.security.virtualizationentitlements to create VMs. From themvm-ctrl/directory:make # release build + sign make install # build, sign, cargo install to ~/.cargo/binFor development,
make sign-debugsigns the debug binary in place. -
Verify
heyvm test-apple-virtThis boots a short-lived default sandbox, confirms SSH works end-to-end, and prints
PASS.heyvm test-apple-virt --detachalso exercises the detached-owner flow.
Default image
On first use, heyvm auto-downloads a prebuilt Alpine Linux image and
exposes it as alpine:3.21. The cache lands at
~/.heyo/images/apple_virt/alpine:3.21/ (kernel + initrd + GRUB EFI +
ext4 rootfs). The rootfs is 4GB, boots into a root shell on hvc0,
runs sshd with a root:heyo password (key auth is provisioned by
heyvm on first use, so password auth is a rarely-used fallback).
Older Apple Virt installs may still have the same base image cached
under default/ or referenced as ubuntu:24.04; heyvm keeps those as
compatibility aliases, but the guest is Alpine.
You can rebuild the default image locally with
mvm-ctrl/install/build-apple-virt-image.sh.
Custom images
Point heyvm wt --image (or heyvm create --image) at a name under
~/.heyo/images/apple_virt/<name>/. Build one with:
heyvm images build --backend-type apple_virt \
--file ./my-base.Dockerfile \
--name my-base \
--size-mb 4096
The builder runs docker buildx build --platform linux/arm64, packs
the container fs into an ext4 image via a privileged alpine helper
container, and copies the vmlinuz/initrd/GRUB binary from the default
image's cache so the image is directly bootable.
Dockerfile requirements:
RUN apk add linux-lts(or equivalent) to get a kernel + initramfs with the right module set baked in. Withoutlinux-ltsinstalled, heyvm injects the netboot kernel/initrd from the default image — works only if those drivers cover your workload./etc/inittabshould respawn/bin/login -f rootonhvc0. The default image's build script (install/build-apple-virt-image.sh) is the canonical working template.sshdrunning on boot (heyvm SSHs in after IP probe).
See the worktree sandboxes guide for the full custom-image + detached + cloud-deploy workflow.
Detach lifecycle files
For each detached sandbox, heyvm writes:
| File | Purpose |
|---|---|
~/.heyo/sandboxes/<id>/sandbox.yaml | Sandbox metadata (image, mounts, TTL, ...) |
~/.heyo/sandboxes/<id>/rootfs.img | Per-sandbox CoW clone of the base image |
~/.heyo/sandboxes/<id>/efi_vars.esp.dmg | EFI boot disk |
~/.heyo/sandboxes/<id>/owner.json | {pid, guest_ip, ssh_port, started_at} — present iff the owner is alive |
~/.heyo/sandboxes/<id>/owner.log | Captured stdout/stderr of the owner process |
Stale owner.json (pid no longer alive) is cleaned up on the next
heyvm list / heyvm get <id>.
Limitations
- macOS-only (Apple silicon).
- Shell-type sandboxes only; no Python/Node runtimes baked into the default image.
- The VM exits when the CLI exits unless
--detachis used. - OS-restart survival (autostart via launchd) is not yet implemented.
heyvm wt --deploy --publish-imagebuilds an amd64 firecracker image for cloud use; the cloud backend is not apple_virt. For workspace-only sync to a libvirt cloud image, useheyvm wt --deploywithout--publish-image.
See also the Apple Container backend
for the container-CLI-based alternative.