vz puts Apple's Virtualization.framework behind a Go API
Create virtual machines and run Linux-based operating systems in Go using Apple Virtualization.framework.
At a glance
- What is it?
- vz is a Go binding for the Virtualization.framework that boots Linux or macOS guests on a Mac, with graphics windows, EFI boot loading, clipboard sharing through a SPICE agent, shared directories and virtio sockets. The hard parts it cannot hide are the entitlement your binary needs, the main thread requirement for the UI, and the fact that API availability depends on both the running macOS and the SDK you built with.
- Who is it for?
- Adopt vz when you are writing a Go tool that needs to boot a Linux guest on a Mac, and you are prepared to sign your binary with the virtualization entitlement and keep the graphic application on the main thread. Do not adopt it as a general purpose hypervisor interface, since it is bound to one vendor framework and one operating system family.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A thin, honest binding rather than a reimplementation
The README is direct about what vz is: it provides the power of the Apple Virtualization.framework in Go. The framework itself is documented by Apple as providing high-level APIs for creating and managing virtual machines on Apple silicon and Intel-based Mac computers, for booting macOS or Linux-based operating systems in custom environments, and it supports the VIRTIO specification, which defines standard interfaces for network, socket, serial port, storage, entropy and memory-balloon devices. So this is not a hypervisor implementation. It is a binding layer, and the design choice that matters is how it calls into the system. The README states that a normal go build compiles the Objective-C helpers and links Apple's frameworks, that the Go bindings use purego for API calls, and that consumers do not need Swift, prebuilt bridge binaries or a generation step. The purego dependency is visible in go.mod at version 0.10.1, alongside golang.org/x/crypto, x/mod and x/sys. That last point has a practical consequence. A binding that calls Objective-C at runtime through purego can be distributed as ordinary Go source and vendored with go mod vendor, which matters when you want to build the same tool on a machine without Xcode. What it cannot avoid is that the target is always macOS, and the API surface is whatever Apple has shipped.
Windows need the main thread, and that is a hard constraint
The window handling is the part of the API that will shape your program's structure. Each VM has its own dispatch queue. To display several VMs in one process, you configure their windows and run one graphic application on the main thread. The README instructs you to call runtime.LockOSThread() in init() to keep the main goroutine there.
func init() {
runtime.LockOSThread()
}
func showWindows(first, second *vz.VirtualMachine) error {
firstWindow, err := vz.NewGraphicWindow(first, 800, 600, vz.WithWindowTitle("First VM"))
if err != nil {
return err
}
secondWindow, err := vz.NewGraphicWindow(second, 800, 600, vz.WithWindowTitle("Second VM"))
if err != nil {
return err
}
return vz.StartGraphicApplication(firstWindow, secondWindow)
}The event loop semantics are worth reading carefully. The first StartGraphicApplication call runs the event loop and returns when the last window closes. While it is running, calling StartGraphicApplication from another goroutine opens more windows, and those calls return when the new windows are displayed. Each call requires at least one window, and each VM can have only one open window. After all windows close, you start the next application on the main thread. Two behaviours are easy to get wrong. Closing a window leaves its VM running and does not affect other windows, while stopping a VM closes its window. And the caller remains responsible for stopping the VMs, so a program that closes its windows without stopping the guests leaves VMs running. For a single VM there is an existing vm.StartGraphicApplication method that follows the same behaviour.
What runs on what hardware
The feature overview in the README is the compatibility matrix in disguise, and it is worth reading line by line. Virtualizing Linux on a Mac is supported on both x86_64 and arm64, with GUI support, a Boot Extensible Firmware Interface ROM, and clipboard sharing through the SPICE agent. Virtualizing macOS is supported on Apple silicon Macs, on arm64 only, and the library fetches the latest restore image supported by the host from the network. The restore mode option is also there, so you can start a macOS guest in recovery mode. Running Intel binaries inside a Linux VM with Rosetta is supported, and only on arm64 hosts, which is the whole point of running an x86 guest image on Apple silicon without an emulator. Then there are the device features. Shared directories have a wiki page of their own, as do Virtio Sockets. The virtio socket support is what makes the kind of tool possible where a guest service is reachable over a socket rather than over a network. The repository layout matches these feature areas file by file: directory_sharing_device.go with separate amd64 and arm64 variants, network.go and network_device.go, clipboard.go, keyboard.go, audio.go, entropy.go, memory_balloon.go, disk.go, bootloader.go and graphics.go, plus bootloader_arm64.go, graphics_arm64.go and keyboard_arm64.go for the architecture specific pieces. The example directory holds three working programs, example/linux, example/macOS and example/gui-linux, along with a .env.example file.
The entitlement is the first thing that will block you
Your binary will not start without a code signature entitlement, and the README says so directly: a process must have the com.apple.security.virtualization entitlement to use the Virtualization APIs. The entitlement is declared in a plist file with the com.apple.security.virtualization key set to true, and the file is applied to the built binary with codesign.
$ codesign --entitlements vz.entitlements -s - <YOUR BINARY PATH>If you also want VZBridgedNetworkDeviceAttachment, the README says you need the com.apple.vm.networking entitlement as well. This is the part that turns a Go project into a macOS project. Signing is not optional, it is not something the library can do for you at runtime, and it has to be redone whenever the binary changes. The -s - form is an ad hoc signature, which is enough for local use and is what the project itself uses in its test flow. The Makefile in the repository does exactly this for its own test binaries. The test target runs go test with an exec wrapper of go run $(PWD)/cmd/codesign, so every test binary is signed on the fly before it runs.
go test -p 1 -exec "go run $(PWD)/cmd/codesign" ./... -timeout 2m -vThe cmd/ directory in the repository holds that codesign helper, and the repository also ships a cmd/vzbridgegen generator used to regenerate the bindings.
Two error values decide whether an API exists
Availability is not a single question on macOS, and this library makes that explicit with a pair of sentinel errors. API availability depends on both the running macOS version and the build SDK. A binary built with an older SDK can return ErrBuildTargetOSVersion for newer APIs, and the README's advice is to build with an SDK that includes the APIs you need. The version compatibility check wraps that in a single pattern.
bootLoader, err := vz.NewEFIBootLoader()
if errors.Is(err, vz.ErrUnsupportedOSVersion) || errors.Is(err, vz.ErrBuildTargetOSVersion) {
return fallbackBootLoader()
}
if err != nil {
return nil, err
}
return bootLoader, nilThere are two items to check. The first is whether the API is compatible with the version of macOS running the program, the second is whether the binary was built with that API enabled. Two distinct errors, two distinct remedies, and a fallback path for each. This is a well designed pattern for a binding that has to work across a range of hosts and SDKs. It is also the reason you should treat the SDK version as part of your build configuration rather than something the developer's machine happens to have. The README frames the wider problem honestly. There is a lot of knowledge required to use Apple's Virtualization.framework, and the information is too scattered and very difficult to understand. The Linux kernel knowledge needed for the virtio features is not documented by Apple at all, so the author compiled what he gathered into the project wiki, which anyone is free to edit.
Requirements, installation and the version you actually get
The requirements are short: macOS Monterey (12.0) or later, Go 1.25 or later, and Xcode or the Command Line Tools with CGO_ENABLED=1. Installation follows the standard Go modules flow. Create a folder, run go mod init github.com/your/repo inside it, and then install the package with go get.
$ go get github.com/Code-Hex/vz/v4There is a version wrinkle worth flagging. The go.mod module path is github.com/Code-Hex/vz/v4 and the install command uses the /v4 suffix, while the README notes that the older v1 and v2 versions are deprecated. The homepage given for the project points at the v3 path on pkg.go.dev, and the tagged releases listed here are v3.8.0, v3.7.1 and v3.7.0. In other words, the module has moved to a major version 4 while the release tags and the homepage still reference the 3.x line. Check which major version your project imports before copying an import path from an older article or tutorial. The licence is MIT with a LICENSE file at the root, and the author of the package is Code-Hex. The last push was on 2026-10-19 and the repository is not archived.
Testing a hypervisor binding without a hypervisor
Testing this kind of library is awkward because a test needs a real guest. The project solves that with a purpose built Linux image. PUI PUI Linux is used to test the library. The README describes it as a Linux designed to provide only the minimum functionality required for the Apple Virtualization.framework, so the kernel file size is very small. The Makefile downloads it on demand and picks the right kernel for the host architecture, mapping arm64 to aarch64 and unpacking a gzip compressed Image on arm64 while moving bzImage into place on other architectures. The version is pinned to 1.0.3.
$ # Download PUI PUI Linux, Only required the first time.
$ make download_kernel
$ make testThe test target itself is unusual and worth understanding. It runs the whole package with -p 1 to serialise the tests, and it uses the exec wrapper so that each test binary is signed before it runs. The timeout is two minutes for the normal suite and five minutes for a targeted run, which is the make test/run variant that takes a TARGET argument. Graphics has its own target, test/graphics, which compiles an Objective-C program with xcrun clang against Cocoa, Virtualization and QuartzCore and runs it. A second variant, test/graphics/multiple, builds the multi window test and then signs the binary with go run ./cmd/codesign before running it, matching the entitlement requirement described earlier. If you want to regenerate the bindings with your installed SDK and Virtualization runtime, the README gives the sequence: go generate ./internal/vzbridge, then go test ./cmd/vzbridgegen ./internal/vzbridge/nativegen, then make test/graphics.
Editorial conclusion
Adopt vz when you are writing a Go tool that needs to boot a Linux guest on a Mac, and you are prepared to sign your binary with the virtualization entitlement and keep the graphic application on the main thread. Do not adopt it as a general purpose hypervisor interface, since it is bound to one vendor framework and one operating system family. Before writing any code, check the SDK you build with against the APIs you intend to call, because a binary built with an older SDK returns ErrBuildTargetOSVersion rather than a clear runtime failure.
Frequently asked questions
What are the requirements for using vz?
The README lists macOS Monterey (12.0) or later, Go 1.25 or later, and Xcode or the Command Line Tools with CGO_ENABLED=1. A normal go build compiles the Objective-C helpers and links Apple's frameworks.
How do I install the vz package?
Create a folder, run go mod init github.com/your/repo inside it, and then run go get github.com/Code-Hex/vz/v4. The older v1 and v2 versions are deprecated.
Why does my vz binary fail to start virtual machines?
A process must have the com.apple.security.virtualization entitlement to use the Virtualization APIs. The README shows creating an entitlements file with that key and applying it with codesign --entitlements vz.entitlements -s - on your binary.
How do I show several virtual machine windows at once?
Each VM has its own dispatch queue, so you create a window per VM with vz.NewGraphicWindow and pass them to vz.StartGraphicApplication, running one graphic application on the main thread and calling runtime.LockOSThread() in init(). Each VM can have only one open window.
What does vz.ErrUnsupportedOSVersion mean?
It means the API is not compatible with the version of macOS running the program. The separate vz.ErrBuildTargetOSVersion means the binary was built with an SDK that does not enable that API, and the README advises building with an SDK that includes the APIs you need.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/code-hex-vz)Was this useful?