How to Use Virtualization.framework with Swift on Apple Silicon: A Complete Guide

Virtualization.framework on Apple Silicon lets you create macOS and Linux VMs in Swift by configuring a VZVirtualMachine, validating it, and calling start() with platform-specific options.

The Lakr233/vphone-cli repository provides a production-ready reference implementation. It demonstrates every step from hardware model creation to UI integration using pure Swift and Apple's native virtualization APIs. This guide walks through the same architecture used to run a virtual iPhone on Apple Silicon Macs.


Prerequisites and Platform Requirements

Virtualization.framework is Apple Silicon only. The VZMac* classes used throughout this guide are unavailable on Intel Macs.

Your project must target:

  • macOS 15 or later (Sequoia)
  • Apple Silicon CPU (M1/M2/M3/M4 series)
  • Swift 6 and Xcode 15+

The vphone-cli source enforces these requirements at runtime by checking ProcessInfo.processInfo.isOperatingSystemAtLeast and verifying the hardware model identifier (PV = 3) exists.


Step 1: Import the Framework

Every file interacting with the VM begins with the framework import. In VPhoneVirtualMachine.swift, this single line enables the entire API surface:

import Virtualization

No bridging header or C++ interop is required. Virtualization.framework is a first-class Swift framework on macOS 15.


Step 2: Create the Hardware Model

Apple Silicon VMs require a VZMacHardwareModel that defines the virtual platform version. The vphone-cli project uses PV = 3, which corresponds to the virtual iPhone hardware:

// VPhoneVirtualMachine.swift:49-52
let hardwareModel = try VPhoneHardware.createModel()

A hardware model is immutable once created. You persist it alongside your VM and reuse it for subsequent launches. Changing hardware models mid-lifecycle invalidates the VM state.


Step 3: Manage Machine Identity and NVRAM

Every macOS VM needs a stable VZMacMachineIdentifier and auxiliary NVRAM storage. The identifier must persist across reboots; NVRAM stores boot arguments and firmware variables.

In VPhoneVirtualMachine.swift:55-84, the code either loads an existing identifier from a manifest or generates a fresh one:

let machineIdentifier = VZMacMachineIdentifier()

// Create auxiliary storage for NVRAM variables
let auxStorage = try VZMacAuxiliaryStorage(
    creatingStorageAt: storageURL,
    hardwareModel: hardwareModel,
    options: .allowOverwrite
)

The project then writes boot arguments to enable serial console output (VPhoneVirtualMachine.swift:36-50):

let bootArgs = "serial=3 debug=0x104c04"
// Private API usage in vphone-cli; production code uses setData:forKey:
Dynamic(auxStorage)._setDataValue(
    bootArgs.data(using: .utf8)!,
    forNVRAMVariableNamed: "boot-args"
)

Step 4: Configure the macOS Platform

The VZMacPlatformConfiguration ties together hardware model, machine identifier, and NVRAM. This is specific to Apple Silicon VMs—Linux VMs use VZGenericPlatformConfiguration instead.

From VPhoneVirtualMachine.swift:106-135:

let platform = VZMacPlatformConfiguration()
platform.hardwareModel = hardwareModel
platform.machineIdentifier = machineIdentifier
platform.auxiliaryStorage = auxStorage

Platform configuration is validated automatically when you build the final VM configuration. Mismatched hardware models and identifiers produce clear error messages at validation time.


Step 5: Build the Complete VM Configuration

VZVirtualMachineConfiguration aggregates all device and resource settings. The vphone-cli implementation (VPhoneVirtualMachine.swift:152-224) demonstrates a comprehensive setup:

CPU and Memory

let config = VZVirtualMachineConfiguration()

// Enforce minimums automatically
config.cpuCount = max(4, VZVirtualMachineConfiguration.minimumAllowedCPUCount)
config.memorySize = max(4 * 1024 * 1024 * 1024,
                       VZVirtualMachineConfiguration.minimumAllowedMemorySize)

Graphics Display

let graphics = VZMacGraphicsDeviceConfiguration()
graphics.displays = [
    VZMacGraphicsDisplayConfiguration(
        widthInPixels: 1290,
        heightInPixels: 2796,
        pixelsPerInch: 460  // iPhone Retina density
    )
]
config.graphicsDevices = [graphics]

Audio Devices

let sound = VZVirtioSoundDeviceConfiguration()
sound.streams = [
    VZVirtioSoundDeviceInputStreamConfiguration(),   // host mic → guest
    VZVirtioSoundDeviceOutputStreamConfiguration()   // guest audio → host speakers
]
config.audioDevices = [sound]

Storage

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

Serial Port (PL011 UART)

The virtual iPhone uses a PL011 serial controller for console access. vphone-cli accesses this through private API wrappers:

// Private API: _VZPL011SerialPortConfiguration
let serial = Dynamic._VZPL011SerialPortConfiguration().asObject as! VZSerialPortConfiguration

let pipeIn = Pipe()
let pipeOut = Pipe()
serial.attachment = VZFileHandleSerialPortAttachment(
    fileHandleForReading: pipeIn.fileHandleForReading,
    fileHandleForWriting: pipeOut.fileHandleForWriting
)
config.serialPorts = [serial]

Optional: VSocket for Host-Guest Communication

config.socketDevices = [VZVirtioSocketDeviceConfiguration()]

This creates a vsock channel used by vphone-cli's control daemon (vphoned) to exchange JSON commands between host and guest.


Step 6: Validate and Instantiate the VM

Configuration errors surface during validation, before any resources are allocated. From VPhoneVirtualMachine.swift:291-296:

try config.validate()

let virtualMachine = VZVirtualMachine(configuration: config)

validate() throws VZError with specific codes for unsupported configurations—helpful for debugging memory size mismatches or unavailable device types.


Step 7: Start the VM with Options

Apple Silicon VMs support platform-specific start options. The vphone-cli project implements DFU mode forcing for firmware development:

let startOptions = VZMacOSVirtualMachineStartOptions()

// Private API for DFU mode; standard API available for normal boot
Dynamic(startOptions)._setForceDFU(true)

try await virtualMachine.start(options: startOptions)

The start(options:) method is asynchronous and throws on immediate failures. Guest crashes or panics are reported through the delegate instead.


Step 8: Handle VM Lifecycle Events

Assign a VZVirtualMachineDelegate to respond to guest state changes. VPhoneVirtualMachine.swift:81-97 adopts this protocol:

extension VPhoneVirtualMachine: VZVirtualMachineDelegate {
    func guestDidStop(_ virtualMachine: VZVirtualMachine) {
        // Clean up resources, notify UI
    }
    
    func virtualMachine(_ vm: VZVirtualMachine, didStopWithError error: Error) {
        // Log crash details, offer restart
    }
}

Delegate methods execute on the VM's internal queue. Dispatch to the main queue for UI updates.


Step 9: Integrate with AppKit UI

Display the VM in a native window using VZVirtualMachineView. The VPhoneAppDelegate.swift file orchestrates this:

let vmView = VZVirtualMachineView()
vmView.virtualMachine = virtualMachine

let window = NSWindow(
    contentRect: NSRect(x: 0, y: 0, width: 720, height: 1440),
    styleMask: [.titled, .closable, .resizable],
    backing: .buffered,
    defer: false
)
window.contentView = vmView
window.makeKeyAndOrderFront(nil)

VZVirtualMachineView handles input forwarding, display scaling, and cursor capture automatically. Subclass it to intercept keyboard events for special key combinations, as VPhoneVirtualMachineView.swift demonstrates.


Advanced: Synthetic Devices and Debugging

The vphone-cli source includes several sophisticated configurations worth studying:

Feature Implementation Source Location
Synthetic battery VZMacSyntheticBatterySource VPhoneVirtualMachine.swift:47-60
GDB debug stub VZGDBDebugStubConfiguration VPhoneVirtualMachine.swift:47-60
USB touch screen VZUSBTouchScreenConfiguration VPhoneVirtualMachine.swift:47-60

These are optional but demonstrate the full capability of Virtualization.framework on Apple Silicon.


Summary

  • Import Virtualization.framework and target macOS 15+ on Apple Silicon only
  • Create VZMacHardwareModel with your platform version, then persist it
  • Generate VZMacMachineIdentifier and VZMacAuxiliaryStorage for NVRAM
  • Assemble VZMacPlatformConfiguration linking all platform components
  • Build VZVirtualMachineConfiguration with CPU, memory, graphics, storage, audio, serial, and network devices
  • Validate with config.validate() before instantiating VZVirtualMachine
  • Start asynchronously using VZMacOSVirtualMachineStartOptions for platform-specific behavior
  • Implement VZVirtualMachineDelegate to respond to guest lifecycle events
  • Display with VZVirtualMachineView in an NSWindow for interactive use

The Lakr233/vphone-cli repository provides working implementations of every step above, making it the definitive reference for Virtualization.framework with Swift on Apple Silicon.


Frequently Asked Questions

What makes Virtualization.framework Apple Silicon only?

The VZMacHardwareModel, VZMacPlatformConfiguration, and related classes implement Apple's proprietary ARM virtualization extensions. These leverage the Apple Silicon CPU's EL2 hypervisor mode and memory architecture. Intel Macs expose different virtualization capabilities through Hypervisor.framework instead.

Can I run Linux VMs with the same code?

Yes, with modifications. Linux guests use VZLinuxBootLoader instead of VZMacOSBootLoader, and VZGenericPlatformConfiguration instead of VZMacPlatformConfiguration. The device configuration (Virtio block, network, serial) remains largely identical. The vphone-cli project specifically targets macOS guests with PV=3 hardware.

How do I persist VM state between launches?

Save the VZMacMachineIdentifier data and reuse the same VZMacHardwareModel when recreating the configuration. The NVRAM file (VZMacAuxiliaryStorage) must also persist at a stable path. Do not regenerate identifiers—doing so presents a new virtual machine to the guest OS, invalidating activation state and licenses.

Why does my VM fail validation with "configuration is not supported"?

Check three common causes: insufficient host memory for requested memorySize, cpuCount below minimumAllowedCPUCount, or attempting to use VZMacGraphicsDeviceConfiguration on an incompatible hardware model. Validation errors include specific VZErrorCode values that pinpoint the failing configuration section.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →