Skip to content
daniel@cosenza:~/blog
macOSDeep Dive June 28, 2026 3 min read

Building Lightweight VMs with Apple's Virtualization.framework

How Virtualization.framework exposes Apple Silicon's hardware virtualization support directly to Swift applications, without a third-party hypervisor.

For years, running a VM on a Mac meant reaching for a third-party product — Parallels, VMware Fusion, or VirtualBox — each implementing its own hypervisor logic on top of lower-level platform APIs. Virtualization.framework, introduced alongside Apple Silicon support, changed that by giving developers a native, Apple-maintained API for creating and running virtual machines directly, without any third-party hypervisor engine underneath.

Built on Hypervisor.framework, exposed at a higher level

Virtualization.framework sits above the lower-level Hypervisor.framework (which exposes the CPU’s virtualization extensions much more directly, closer to how bhyve or KVM expose theirs on other platforms). Virtualization.framework’s contribution is a much higher-level, Swift/Objective-C-native API — virtual machine configuration, storage, and networking are all expressed as ordinary Swift objects rather than raw hypervisor calls.

import Virtualization

let config = VZVirtualMachineConfiguration()
config.cpuCount = 4
config.memorySize = 4 * 1024 * 1024 * 1024  // 4 GiB

Configuring boot: Linux guests and macOS guests differ

Booting a Linux guest uses VZLinuxBootLoader, pointing directly at a kernel image and initial ramdisk — no BIOS or UEFI emulation involved, since the framework boots the kernel directly, similar in spirit to how a cloud hypervisor often boots a guest kernel directly rather than emulating firmware:

let bootLoader = VZLinuxBootLoader(kernelURL: kernelURL)
bootLoader.initialRamdiskURL = initrdURL
bootLoader.commandLine = "console=hvc0 root=/dev/vda"
config.bootLoader = bootLoader

Running a macOS guest instead uses VZMacOSBootLoader along with a restore image fetched through VZMacOSRestoreImage, and requires a VZMacHardwareModel and auxiliary storage tied to the specific host — reflecting that macOS-on-macOS virtualization is a first-class, Apple-supported scenario, not a workaround.

let bootLoader = VZMacOSBootLoader()
config.bootLoader = bootLoader
config.platform = VZMacPlatformConfiguration()

Storage and networking as composable device objects

Each virtual device is its own configuration object, attached to the VM configuration before it starts — a pattern that reads similarly to assembling a bhyve command line’s -s slot,device flags, just expressed as Swift objects instead of shell arguments:

let disk = try VZDiskImageStorageDeviceAttachment(url: diskURL, readOnly: false)
let storageDevice = VZVirtioBlockDeviceConfiguration(attachment: disk)
config.storageDevices = [storageDevice]

let network = VZNATNetworkDeviceAttachment()
let networkDevice = VZVirtioNetworkDeviceConfiguration()
networkDevice.attachment = network
config.networkDevices = [networkDevice]

VZNATNetworkDeviceAttachment gives the guest outbound network access through NAT with essentially zero configuration — the framework handles the translation, much like a consumer virtualization product’s default networking mode, without needing to manually set up a bridge interface the way a bare-metal bhyve configuration would.

Starting the machine

Once fully configured, validation and startup are asynchronous operations on the VZVirtualMachine object itself:

try config.validate()
let vm = VZVirtualMachine(configuration: config)

vm.start { result in
    switch result {
    case .success:
        print("VM started")
    case .failure(let error):
        print("Failed to start: \(error)")
    }
}

Why this exists as a first-party framework

Apple Silicon’s transition made third-party hypervisors’ job considerably harder — the architecture change meant Parallels, VMware, and VirtualBox all needed substantial rework, and Rosetta-translated x86 guests are a fundamentally different (and slower) proposition than native ARM virtualization. Virtualization.framework gave developers (and Apple’s own tools, including elements of Xcode’s iOS Simulator infrastructure) a supported, native path to lightweight virtualization that doesn’t depend on any third party having solved Apple Silicon compatibility first — command-line tools like tart and lume, popular for CI runners that need disposable macOS or Linux VMs, are built directly on top of this framework rather than wrapping an external hypervisor.

Where it fits relative to containers and full VMs

Virtualization.framework VMs are genuinely separate kernels with hardware-enforced isolation — a meaningfully stronger boundary than a Linux container’s shared-kernel namespace isolation, at a higher resource cost than a container but a much lower one than a traditional full-emulation VM product. For CI runners needing a disposable, fully isolated macOS or Linux environment per job, or for running a specific Linux userland alongside macOS without dual-booting, it’s become the default modern answer specifically because it’s native, actively maintained by Apple, and doesn’t carry a third-party hypervisor’s own compatibility lag behind new hardware.