tomato·bazeldocs v0 · latest
Docs/Reference/Modules/rules_macvm

rules_macvm

Bazel-idiomatic, hermetic Linux VMs on macOS via Apple Virtualization.framework (vfkit), with a pluggable VMM provider seam.

Latest0.0.1
Versions1
CategoryBazel rules
MaintainersMatt Marshall
Registryhttps://registry.tbzl.dev/modules/rules_macvm/
Sourcegithub.com/tomato-bazel/rules_macvm
MODULE.bazelstarlark
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 vm rule: declare a VM (cpus, memory, Linux-kernel or EFI boot, virtio-blk disks, Rosetta, Ignition, nested, raw --device escape hatch) → a bazel run-able target that boots it, plus a VmInfo provider and a deterministic <name>.argv manifest.
  • A provider seam: //vm:toolchain_type + the vm_provider rule. 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 (pure json.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

kindstatusnotes
vfkitsupportedApple Virtualization.framework, signed binary pinned
mocktest-onlyin-repo fake VMM; vfkit-CLI-compatible, for hermetic tests
krunkitextension pointlibkrun/GPU microVMs — add a translator + fetch
qemuextension pointqemu-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

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
enable_unitssystemd unit names to enable (e.g. podman.socket).List of stringsoptional[]
ssh_authorized_keysSSH public keys authorized for username.List of stringsoptional[]
usernameUser to attach ssh_authorized_keys to.Stringoptional"core"

VmIgnitionInfo

load("@rules_macvm//image:defs.bzl", "VmIgnitionInfo")

VmIgnitionInfo(file)

A generated Ignition provisioning file.

FIELDS

NameDescription
fileFile: 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

NameDescriptionTypeMandatoryDefault
versionOverride the vfkit version.Stringoptional""

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

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
cloud_initcloud-init files: user-data and (optionally) meta-data.List of labelsoptional[]
cpusNumber of virtual CPUs.Integeroptional1
devicesRaw --device specs (virtio-net, virtio-vsock, virtio-gpu, …).List of stringsoptional[]
disksDisk images attached as virtio-blk devices.List of labelsoptional[]
efiEFI-boot a disk image (instead of a direct kernel).BooleanoptionalFalse
extra_argsRaw flags appended to the VMM invocation.List of stringsoptional[]
guiOpen a graphical window for the VM.BooleanoptionalFalse
ignitionIgnition provisioning file (FCOS-style).LabeloptionalNone
initrdinitrd/initramfs for direct boot.LabeloptionalNone
kernelLinux kernel image for direct boot.LabeloptionalNone
kernel_cmdlineLinux kernel command line.Stringoptional""
memoryRAM, e.g. “2GiB” / “512MiB” / a MiB integer.Stringoptional"512MiB"
nestedEnable nested virtualization (M3+).BooleanoptionalFalse
providerVMM backend override. If unset, the registered //vm:toolchain_type toolchain is used.LabeloptionalNone
restful_uriURI for the VMM’s RESTful lifecycle control plane.Stringoptional""
rosettaExpose Rosetta x86-64 translation (virtio share rosetta).BooleanoptionalFalse

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 the vm_provider rule; resolved by vm either through the //vm:toolchain_type toolchain or an explicit provider attribute.

  • 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

NameDescription
kindString: the provider kind this VM was built for.
cpusInt: virtual CPU count.
memory_mibInt: RAM in MiB.
launcherFile: the executable that boots the VM (also the rule’s DefaultInfo executable).
argv_manifestFile: deterministic, host-independent record of the VMM command line (short_paths, not resolved absolutes). For golden tests and bazel build-time inspection.

VmProviderInfo

load("@rules_macvm//vm:providers.bzl", "VmProviderInfo")

VmProviderInfo(kind, vmm, supports)

A VMM backend that can launch a vm.

FIELDS

NameDescription
kindString: backend identity (e.g. “vfkit”, “mock”). Selects the argv translator in //vm/private:argv.bzl.
vmmTarget: the hypervisor executable (its runfiles ride along).
supportsstruct: capability flags — rosetta, virtiofs, vsock, nested, efi_boot, linux_boot. Used for early, clear validation errors.

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

NameDescriptionDefault 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

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
efi_bootBackend can EFI-boot a disk image.BooleanoptionalFalse
kindBackend identity; selects the argv translator (“vfkit”, “mock”, …).Stringrequired
linux_bootBackend can direct-boot a Linux kernel + initrd.BooleanoptionalFalse
nestedBackend supports nested virtualization.BooleanoptionalFalse
rosettaBackend can expose Rosetta x86-64 translation.BooleanoptionalFalse
virtiofsBackend supports virtio-fs directory shares.BooleanoptionalFalse
vmmThe hypervisor executable target.Labelrequired
vsockBackend supports virtio-vsock.BooleanoptionalFalse

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.

AtomResolved hereElsewhere
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#

rules_macvm in the registry graph — what it depends on (left) and what depends on it (right).

Depends on

platforms1.0.0bazel_skylib1.8.2rules_shell0.6.1devstardoc0.7.2dev

Used by (1 in the registry)

Versions#

1 published version, newest first. Each resolves to an immutable, integrity-checked archive.

VersionIntegrity (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.
  • vm rule (//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 raw devices/extra_args escape hatch. Emits VmInfo and a deterministic <name>.argv manifest.
  • Provider seam: //vm:toolchain_type, the vm_provider rule (returns both VmProviderInfo and ToolchainInfo — usable per-target via provider = … or as a registered toolchain), and VmProviderInfo / VmInfo providers. Spec→argv translation isolated in //vm/private:argv.bzl with 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 the com.apple.security.virtualization entitlement; 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_config renders an Ignition (FCOS-style) provisioning file from typed attrs via json.encode.
  • tools/refresh_versions.py re-pins vfkit from the GitHub API; stardoc reference under docs/; //examples/smoke coverage — golden argv + golden Ignition tests, a mock-backed boot test, build_test, and a macOS-gated vfkit signature/entitlement test.

← All modules