rules_macvm
Bazel-idiomatic, hermetic Linux VMs on macOS via Apple Virtualization.framework (vfkit), with a pluggable VMM provider seam.
| Latest | 0.0.1 |
|---|---|
| Versions | 1 |
| Category | Bazel rules |
| Maintainers | Matt Marshall |
| Registry | https://registry.tbzl.dev/modules/rules_macvm/ |
| Source | github.com/tomato-bazel/rules_macvm |
bazel_dep(name = "rules_macvm", version = "0.0.1")
View source & releases on GitHub ↗
Bazel-idiomatic, hermetic Linux VMs on macOS via Apple Virtualization.framework — declarative VM targets, a pinned VMM toolchain, and a pluggable provider seam (vfkit first).
Status: v0.0.1
- A
vmrule: declare a VM (cpus, memory, Linux-kernel or EFI boot, virtio-blk disks, Rosetta, Ignition, nested, raw--deviceescape hatch) → abazel run-able target that boots it, plus aVmInfoprovider and a deterministic<name>.argvmanifest. - A provider seam:
//vm:toolchain_type+ thevm_providerrule. Backends are swappable per-target (provider = …) or by registered toolchain. Adding one is a translator function + a registration. - The vfkit provider: hermetically fetches the pinned, signed universal vfkit binary (entitlement-carrying) and exposes it as a toolchain (macOS-exec-constrained).
- A mock provider: an in-repo, vfkit-CLI-compatible fake VMM so the whole rule pipeline is tested in CI with no Virtualization.framework.
image/:ignition_config— render an Ignition provisioning file from typed attrs (purejson.encode, golden-tested).
See CHANGELOG.md and generated reference in docs/.
Why this exists
macOS has no Linux kernel, so containers and Linux toolchains need a VM.
Apple’s Virtualization.framework lets a userspace process boot one with
no kernel extension; vfkit is a thin, scriptable frontend to it. This
ruleset makes a VM a declarative, pinned, hermetic Bazel target —
the substrate for hermetic Linux build/test on a Mac, reproducible VM
images, Rosetta-accelerated x86-64, and microVM sandboxing. (Podman’s
podman machine is one such consumer; rules_podman can depend on this.)
Install
.bazelrc:
common --registry=https://registry.fastverk.com/
common --registry=https://bcr.bazel.build/
MODULE.bazel:
bazel_dep(name = "rules_macvm", version = "0.0.1")
vfkit = use_extension("@rules_macvm//providers/vfkit:extensions.bzl", "vfkit")
use_repo(vfkit, "vfkit")
register_toolchains("@vfkit//:vfkit_toolchain_def")
Usage
load("@rules_macvm//vm:defs.bzl", "vm")
load("@rules_macvm//image:defs.bzl", "ignition_config")
ignition_config(
name = "provision",
ssh_authorized_keys = ["ssh-ed25519 AAAA… you@host"],
enable_units = ["podman.socket"],
)
# `bazel run //:dev` boots it via the registered vfkit toolchain.
vm(
name = "dev",
cpus = 4,
memory = "4GiB",
kernel = "//path:vmlinuz", # or efi = True to boot a disk
initrd = "//path:initrd.img",
kernel_cmdline = "console=hvc0 root=/dev/vda",
disks = ["//path:rootfs.img"],
ignition = ":provision",
rosetta = True, # x86-64 translation on Apple Silicon
nested = True, # M3+
devices = ["virtio-net,nat", "virtio-vsock,port=1024,socketURL=unix:///tmp/v.sock"],
)
bazel build //:dev also emits dev.argv — the exact VMM command line
(host-independent short_paths) for review and golden tests.
Providers
| kind | status | notes |
|---|---|---|
vfkit | supported | Apple Virtualization.framework, signed binary pinned |
mock | test-only | in-repo fake VMM; vfkit-CLI-compatible, for hermetic tests |
krunkit | extension point | libkrun/GPU microVMs — add a translator + fetch |
qemu | extension point | qemu-hvf — add a translator + fetch |
Adding a provider: implement _<kind>_tokens(spec) in
vm/private/argv.bzl, add a branch in
build_tokens, and register a vm_provider + toolchain.
The hard constraint: testing
Virtualization.framework needs a real Mac host with the entitlement,
and (pre-M3) couldn’t nest in cloud CI. So CI coverage is:
golden argv + generated-Ignition tests, the mock-backed boot test
(full pipeline, no VZ), build_test analysis, and a macOS-gated vfkit
signature/entitlement test (skipped on Linux). Actual VM boots run on
self-hosted/local Macs; --nested on M3+ may open up more. Bring your
own guest artifacts (kernel/initrd or a bootable disk) — rootfs assembly
is roadmap, not v0.0.1.
Maintenance
tools/refresh_versions.py # re-pin vfkit to latest stable
tools/refresh_versions.py --version 0.6.3
bazel run //docs:update # regenerate committed rule docs Usage#
Real usage, taken from the module’s examples/.
examples/smoke/BUILD.bazel
load("@bazel_skylib//rules:build_test.bzl", "build_test")
load("@bazel_skylib//rules:diff_test.bzl", "diff_test")
load("@rules_macvm//image:defs.bzl", "ignition_config")
load("@rules_macvm//vm:defs.bzl", "vm")
load("@rules_shell//shell:sh_test.bzl", "sh_test")
# A representative Linux VM built against the mock backend — hermetic, no
# Virtualization.framework. `bazel run //examples/smoke:linux_vm` "boots"
# it (the mock prints the resolved argv).
vm(
name = "linux_vm",
cpus = 2,
devices = [
"virtio-net,nat",
"virtio-rng",
],
disks = ["rootfs.img"],
ignition = ":provision",
initrd = "initrd.img",
kernel = "vmlinuz",
kernel_cmdline = "console=hvc0 root=/dev/vda",
memory = "2GiB",
nested = True,
provider = "@rules_macvm//providers/mock:mock_provider",
rosetta = True,
)
# Ignition provisioning rendered purely from attrs (hermetic JSON).
ignition_config(
name = "provision",
enable_units = ["podman.socket"],
ssh_authorized_keys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID demo@rules_macvm"],
)
# Golden: the resolved vfkit-style command line is stable and correct.
# Regenerate with: bazel run //examples/smoke:update_argv_golden
diff_test(
name = "argv_golden_test",
file1 = "linux_vm.argv.golden",
file2 = "linux_vm.argv",
)
# Golden for the generated Ignition JSON.
diff_test(
name = "ignition_golden_test",
file1 = "provision.ign.golden",
file2 = "provision.ign",
)
# Hermetic boot smoke: launcher resolves the backend + runfiles and the
# (mock) VMM completes.
sh_test(
name = "boot_smoke_test",
srcs = ["boot_smoke.sh"],
args = ["$(rootpath :linux_vm)"],
data = [":linux_vm"],
)
# An EFI-boot VM (disk with its own bootloader) — analysis coverage of
# the other boot path.
vm(
name = "efi_vm",
disks = ["rootfs.img"],
efi = True,
memory = "1GiB",
provider = "@rules_macvm//providers/mock:mock_provider",
)
build_test(
name = "analysis_test",
targets = [
":linux_vm",
":efi_vm",
":provision",
],
)
# macOS-only: the fetched vfkit is signed, entitled, and runs. Skipped on
# Linux CI (exec-incompatible).
sh_test(
name = "vfkit_signature_test",
srcs = ["vfkit_signature.sh"],
args = ["$(rootpath @vfkit//:vfkit)"],
data = ["@vfkit"],
target_compatible_with = ["@platforms//os:macos"],
)Rules & providers#
Generated with Stardoc from the module's .bzl sources.
from docs/image.md
Guest-image helpers for rules_macvm.
Provider-agnostic: the artifacts here boot under vfkit, krunkit, qemu, or
bare metal. v0.0.1 ships ignition_config — generate an Ignition
(Fedora CoreOS-style) provisioning file from typed attrs, entirely with
Starlark json.encode (hermetic, golden-testable). Feed it to a VM via
vm(ignition = ":provision").
Roadmap (documented, not yet built): rootfs assembly from OCI layers / mkosi, kernel+initrd extraction, inline-file Ignition stanzas (need an encoder tool), and butane → Ignition transpilation.
ignition_config
load("@rules_macvm//image:defs.bzl", "ignition_config")
ignition_config(name, enable_units, ssh_authorized_keys, username)
Render an Ignition provisioning JSON from typed attrs. Output: <name>.ign.
ATTRIBUTES
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| enable_units | systemd unit names to enable (e.g. podman.socket). | List of strings | optional | [] |
| ssh_authorized_keys | SSH public keys authorized for username. | List of strings | optional | [] |
| username | User to attach ssh_authorized_keys to. | String | optional | "core" |
VmIgnitionInfo
load("@rules_macvm//image:defs.bzl", "VmIgnitionInfo")
VmIgnitionInfo(file)
A generated Ignition provisioning file.
FIELDS
| Name | Description |
|---|---|
| file | File: the rendered Ignition JSON. |
from docs/vfkit.md
Module extension for the vfkit VMM backend.
Hermetically fetches the pinned, signed universal vfkit binary and emits
@vfkit//:vfkit (the binary), @vfkit//:vfkit_provider (a
vm_provider usable via vm(provider = ...)), and
@vfkit//:vfkit_toolchain_def (the same provider registered for
//vm:toolchain_type, constrained to a macOS exec platform since
Virtualization.framework is macOS-only).
vfkit = use_extension("@rules_macvm//providers/vfkit:extensions.bzl", "vfkit")
use_repo(vfkit, "vfkit")
register_toolchains("@vfkit//:vfkit_toolchain_def")
vfkit
vfkit = use_extension("@rules_macvm//providers/vfkit:extensions.bzl", "vfkit")
vfkit.toolchain(version)
Sets up @vfkit: the pinned signed vfkit binary + a vm_provider + a macOS toolchain.
TAG CLASSES
toolchain
Attributes
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| version | Override the vfkit version. | String | optional | "" |
from docs/vm_defs.md
The vm rule — declare and boot a virtual machine.
vm is both a launchable target (bazel run //:dev boots it via the
resolved VMM backend) and a VmInfo provider other rules can consume.
It emits a deterministic <name>.argv manifest of the VMM command line
for golden testing and bazel build-time inspection.
The backend is resolved from the provider attribute if set, else from
a registered //vm:toolchain_type toolchain. Common devices are typed
attributes; devices / extra_args are raw passthroughs for the long
tail (virtio-net, virtio-vsock, virtio-gpu, …).
vm
load("@rules_macvm//vm:defs.bzl", "vm")
vm(name, cloud_init, cpus, devices, disks, efi, extra_args, gui, ignition, initrd, kernel,
kernel_cmdline, memory, nested, provider, restful_uri, rosetta)
Declare and boot a VM via a resolved VMM backend. Emits VmInfo + a <name>.argv manifest.
ATTRIBUTES
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| cloud_init | cloud-init files: user-data and (optionally) meta-data. | List of labels | optional | [] |
| cpus | Number of virtual CPUs. | Integer | optional | 1 |
| devices | Raw --device specs (virtio-net, virtio-vsock, virtio-gpu, …). | List of strings | optional | [] |
| disks | Disk images attached as virtio-blk devices. | List of labels | optional | [] |
| efi | EFI-boot a disk image (instead of a direct kernel). | Boolean | optional | False |
| extra_args | Raw flags appended to the VMM invocation. | List of strings | optional | [] |
| gui | Open a graphical window for the VM. | Boolean | optional | False |
| ignition | Ignition provisioning file (FCOS-style). | Label | optional | None |
| initrd | initrd/initramfs for direct boot. | Label | optional | None |
| kernel | Linux kernel image for direct boot. | Label | optional | None |
| kernel_cmdline | Linux kernel command line. | String | optional | "" |
| memory | RAM, e.g. “2GiB” / “512MiB” / a MiB integer. | String | optional | "512MiB" |
| nested | Enable nested virtualization (M3+). | Boolean | optional | False |
| provider | VMM backend override. If unset, the registered //vm:toolchain_type toolchain is used. | Label | optional | None |
| restful_uri | URI for the VMM’s RESTful lifecycle control plane. | String | optional | "" |
| rosetta | Expose Rosetta x86-64 translation (virtio share rosetta). | Boolean | optional | False |
from docs/vm_providers.md
Core providers for rules_macvm.
Two providers carry the data the rules pass around:
-
VmProviderInfo— a VMM backend (vfkit, mock, …): the hypervisor binary plus its identity (kind) and capability flags. Produced by thevm_providerrule; resolved byvmeither through the//vm:toolchain_typetoolchain or an explicitproviderattribute. -
VmInfo— a declared virtual machine: the resolved spec scalars, the launcher, and the deterministic argv manifest. Lets other rules introspect / depend on a VM without re-deriving its definition.
VmInfo
load("@rules_macvm//vm:providers.bzl", "VmInfo")
VmInfo(kind, cpus, memory_mib, launcher, argv_manifest)
A declared virtual machine.
FIELDS
VmProviderInfo
load("@rules_macvm//vm:providers.bzl", "VmProviderInfo")
VmProviderInfo(kind, vmm, supports)
A VMM backend that can launch a vm.
FIELDS
provider_supports
load("@rules_macvm//vm:providers.bzl", "provider_supports")
provider_supports(*, rosetta, virtiofs, vsock, nested, efi_boot, linux_boot)
Construct the supports capability struct for a VmProviderInfo.
PARAMETERS
| Name | Description | Default Value |
|---|---|---|
| rosetta | - | False |
| virtiofs | - | False |
| vsock | - | False |
| nested | - | False |
| efi_boot | - | False |
| linux_boot | - | False |
from docs/vm_toolchains.md
The vm_provider rule — declares a VMM backend.
A vm_provider target does double duty: it returns VmProviderInfo
(so a vm can reference it directly via its provider attribute) and
platform_common.ToolchainInfo (so it can be registered for
//vm:toolchain_type and resolved automatically by platform). One
target, both wiring styles.
Each fetched/built backend declares one:
vm_provider(
name = "vfkit_provider",
kind = "vfkit",
vmm = ":vfkit",
rosetta = True,
virtiofs = True,
vsock = True,
nested = True,
efi_boot = True,
linux_boot = True,
)
toolchain(
name = "vfkit_toolchain_def",
toolchain = ":vfkit_provider",
toolchain_type = "@rules_macvm//vm:toolchain_type",
)
vm_provider
load("@rules_macvm//vm:toolchains.bzl", "vm_provider")
vm_provider(name, efi_boot, kind, linux_boot, nested, rosetta, virtiofs, vmm, vsock)
Declare a VMM backend usable as both a vm provider and a registered toolchain.
ATTRIBUTES
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| efi_boot | Backend can EFI-boot a disk image. | Boolean | optional | False |
| kind | Backend identity; selects the argv translator (“vfkit”, “mock”, …). | String | required | |
| linux_boot | Backend can direct-boot a Linux kernel + initrd. | Boolean | optional | False |
| nested | Backend supports nested virtualization. | Boolean | optional | False |
| rosetta | Backend can expose Rosetta x86-64 translation. | Boolean | optional | False |
| virtiofs | Backend supports virtio-fs directory shares. | Boolean | optional | False |
| vmm | The hypervisor executable target. | Label | required | |
| vsock | Backend supports virtio-vsock. | Boolean | optional | False |
Conformance#
No gate findings. 8 contested atoms. See how gating works or the full report.
Contested atoms
Third-party modules where this module resolves a different version than others do. Not a violation of anything this module did — it is the actionable form of a registry-level convergence finding, and the sentence a maintainer can act on.
| Atom | Resolved here | Elsewhere |
|---|---|---|
apple_support | 1.24.2 | 2.2.0 ×1 |
bazel_skylib | 1.8.2 | 1.9.0 ×2 |
nlohmann_json | 3.6.1 | 3.12.0.bcr.1 ×1 |
protobuf | 33.4 | 34.0.bcr.1 ×2 |
rules_jvm_external | 6.7 | 6.8 ×4 |
rules_python | 1.7.0 | 2.0.1 ×1 |
rules_swift | 3.1.2 | 3.6.1 ×1 |
upb | 0.0.0-20220923-a547704 | 0.0.0-20230516-61a97ef ×1 |
Dependencies#
Depends on
Used by (1 in the registry)
Versions#
1 published version, newest first. Each resolves to an immutable, integrity-checked archive.
| Version | Integrity (sha256) | Source archive |
|---|---|---|
0.0.1 latest | +VxAXVkAG7Bx2geN… | tag archive ↗ |
Changelog#
All notable changes to rules_macvm. The format is loosely Keep a Changelog — version headers mirror the published bazel-registry entries.
0.0.1
- Initial scaffold via
rels scaffold. vmrule (//vm:defs.bzl): declarative,bazel run-able VM targets with typed attrs (cpus, memory, Linux-kernel/EFI boot, virtio-blk disks, rosetta, ignition, cloud_init, nested, gui, restful_uri) plus a rawdevices/extra_argsescape hatch. EmitsVmInfoand a deterministic<name>.argvmanifest.- Provider seam:
//vm:toolchain_type, thevm_providerrule (returns bothVmProviderInfoandToolchainInfo— usable per-target viaprovider = …or as a registered toolchain), andVmProviderInfo/VmInfoproviders. Spec→argv translation isolated in//vm/private:argv.bzlwith a documented per-kind extension point. - vfkit provider (
//providers/vfkit:extensions.bzl%vfkit): hermetic fetch of the pinned signed universal vfkit binary (v0.6.3), preserving thecom.apple.security.virtualizationentitlement; exposed as@vfkit//:vfkit+ a macOS-exec-constrained@vfkit//:vfkit_toolchain_def. - mock provider (
//providers/mock): in-repo vfkit-CLI-compatible fake VMM for hermetic, VZ-free testing of the rule pipeline. image/:ignition_configrenders an Ignition (FCOS-style) provisioning file from typed attrs viajson.encode.tools/refresh_versions.pyre-pins vfkit from the GitHub API; stardoc reference underdocs/;//examples/smokecoverage — golden argv + golden Ignition tests, a mock-backed boot test,build_test, and a macOS-gated vfkit signature/entitlement test.