MacVisor pulls VM images from OCI registries, such as ghcr.io, Docker Hub, Amazon ECR, Google Artifact Registry, Azure and Harbor, and pushes your own VMs and templates to them. It reads two kinds of image: Tart's, so the public macOS and Linux images made for Tart work as they are, and MacVisor's own format, which is the one it pushes. A container image isn't a VM image, and is refused.
An image you pull becomes a template, as a cloud image does. VMs made from it are linked clones, each a Mac or PC of its own.
Pull an image
mvz images inspect ghcr.io/cirruslabs/debian --tags # what it is, and its tags
mvz images pull ghcr.io/cirruslabs/macos-golden-gate-base:latest # a template, for later VMs
mvz create ghcr.io/cirruslabs/debian:latest deb # pulls it if needed, then makes a VM
mvz clone ghcr.io/cirruslabs/macos-golden-gate-base mac # the same, as tart clone does
A reference is <registry>/<repository>[:<tag>]. Name the registry, as Tart does: ghcr.io/…, docker.io/… or localhost:5000/…. MacVisor takes the first part as a registry when it has a dot or a colon, or is localhost. That keeps a reference apart from a catalogue id such as debian-13, a URL or a file. The tag is latest unless you give one, and a digest (@sha256:…) wins over a tag. On Docker Hub, a repository with no owner gets library/ in front, as in Docker.
images inspectsays what an image is without pulling it: its format and system, its sizes, and when it was pushed.--tagslists the repository's tags.images pullmakes a template. It's named after the repository, with the tag, so the name says where it came from.--namegives another.--locationputs it in another storage location, and--concurrencymoves 1 to 16 chunks at once (4 by default).create <reference> <name>andclone <reference> <name>pull the image into its template first, or use the template that has it already, then make the VM from it. Both take--locationand--insecure, andcreatealso takes--networkand--forward. A missing name, or one that's taken, is refused before anything downloads.--detachreturns once the pull has started, andmvz tasksfollows it.--jsonprints the result as JSON.
What a pulled template is
- A raw disk becomes ASIF as it downloads, with 512-byte sectors, so VMs made from it can be linked clones. Blocks of zeros aren't written.
- An ASIF disk, as Tart's macOS 27 images have, is kept as it is.
- A macOS image keeps its hardware model and auxiliary storage, and a Linux image its EFI variables. Each VM made from either is a Mac or PC of its own.
- VMs made from it don't forward their ports to this Mac unless you turn that on (
--forward, or the switch in VM Settings), as an image is someone else's prepared VM. Cloud-init still makes your account in the guest, without your full name.
Before anything downloads, MacVisor checks that the image is ARM64, that this Mac supports its hardware model, and that it fits on the drive, with the free space MacVisor keeps so that running VMs never pause for lack of it. Every chunk is checked against its digest, and what it unpacks to against the size and digest the image gives for it. A chunk that arrives damaged is fetched again. An image whose own data doesn't match fails the pull.
Pull again, and Update from Registry
Pulling a tag whose image is here already does nothing. MacVisor goes by the image's digest, and uses the template it has in that storage location.
To get a newer image from the same tag, pull it again, or right-click the template and choose Update from Registry. A new image becomes a template of its own, in the same storage location and named after the old one, and VMs made from the old template keep it. When the image hasn't changed, MacVisor says so and downloads nothing. A template pulled by its digest alone has no tag to follow, so it has no Update from Registry.
Cancel and resume
A pull is a task in the Tasks pane. Cancel there, mvz tasks cancel <task-id> or Ctrl-C in mvz stops it, also while it waits for another pull of the same image. A second Ctrl-C ends mvz at once while the service finishes cancelling. What it downloaded stays, and the next pull of the same image carries on from there. A pull cut off by a service restart resumes by itself, with the same --name, --concurrency and --insecure, and mvz service restart doesn't wait for one. Partial pulls left untouched for two weeks are removed.
A resumed pull signs in with MacVisor's own sign-in, or none: credentials mvz passed from your environment or Docker's config were held in memory only. If the registry then refuses it, the task fails and says to sign in with mvz images login.
In the app
- New VM → Registry: pick an image, or Other… for any reference, then a tag and the VM's name. Get Image and Create VM downloads the image if it isn't here, or if its tag now names a newer one, then makes the VM.
- Templates → + → Get an Image from a Registry… makes a template only.
MacVisor suggests images, its own first once there are any, then Tart's under Tart images (third-party). It looks an image up as you choose it: its system and format, the download and disk sizes, and any template here that has it already. Tags lists the repository's tags.
A pull shows in Templates at once, with its progress. Select it to follow the download or cancel it. A template from a registry shows as OCI Template.
A template's Registry settings
A template from a registry has a Registry page in its settings. Repository and Tag are what Update from Registry follows, and you can change them, for example when an image moves to another registry or to follow a fixed tag instead of latest. The rest says what was pulled: when, the disk size, the format and the digest.
mvz images source "debian (cirruslabs)" # where it came from
mvz images source "debian (cirruslabs)" ghcr.io/cirruslabs/debian:13 # follow another tag
Tart's images
Tart's public images on ghcr.io, from Cirrus Labs and OpenAI, work as they are. MacVisor suggests these, marked third-party:
| Image | Repository |
|---|---|
| macOS 27 (vanilla) | ghcr.io/cirruslabs/macos-golden-gate-vanilla |
| macOS 27 with Homebrew | ghcr.io/cirruslabs/macos-golden-gate-base |
| macOS 26 with Homebrew | ghcr.io/cirruslabs/macos-tahoe-base |
| Ubuntu | ghcr.io/cirruslabs/ubuntu |
| Debian | ghcr.io/cirruslabs/debian |
- Sign in as
admin, with the passwordadmin. SSH is on. Change the password in any VM other people can reach. - Stacked images (Tart 2.36 and later) are flattened into one ASIF disk as they're pulled. That briefly takes twice the disk's space, which the space check counts.
- Refused, with the reason: an image in Tart's
disk.v1layout (one stream across all its layers), one for another architecture, one with a hardware model this Mac doesn't support, and one whose layers come in another order or number than Tart writes them, or don't hold what they say.
A VM made from a Tart image is pushed in MacVisor's format, which Tart can't read. See Push a VM or template.
A Tart VM on this Mac, in ~/.tart/vms, comes in with Import instead.
Sign in to a registry
Public images need no sign-in. For private ones, and to push:
echo "$GHCR_TOKEN" | mvz images login ghcr.io --username me --password-stdin
mvz images login registry.example.com # asks for the user name and password
mvz images login --list # registries and user names, never secrets
mvz images logout ghcr.io
MacVisor checks a sign-in with the registry first, then keeps it in this Mac's Keychain, for MacVisor only. Its background service writes and reads it, and never hands the password back to the app or mvz. The password or token comes from standard input or a prompt, never from the command line.
In the app, Settings → Registries lists the sign-ins. Sign In to a Registry… asks for the registry, the user name and the password or token, and the − button beside a sign-in signs out. For ghcr.io, use a token that can read packages, and write them if you push.
Credentials mvz picks up
mvz also passes on credentials from your shell. They're held in memory for that request only: never stored, logged or kept with the task. For each registry, the first of these that has one is used:
| Order | Source | Details |
|---|---|---|
| 1 | Environment variables naming the registry | MVZ_REGISTRY_USERNAME and MVZ_REGISTRY_PASSWORD, with MVZ_REGISTRY_HOSTNAME set to the registry. Tart's TART_REGISTRY_* variables work too, so CI scripts carry over. |
| 2 | MacVisor's sign-in | From mvz images login or Settings → Registries, in the Keychain. |
| 3 | Docker's credentials | Docker's config, in $DOCKER_CONFIG or ~/.docker/config.json, in Docker's order: credHelpers for the registry, then credsStore, then auths. Identity tokens work. Credential helpers are found on your PATH, in Homebrew's bin folders and in Docker.app. |
| 4 | Environment variables naming no registry | The same variables without …_HOSTNAME. They're sent only to a registry that refuses anonymous access, so a CI job's password for its own registry never goes to a public one. |
| 5 | None | Anonymous access. |
The app uses MacVisor's sign-in, or none.
Push a VM or template
mvz images push dev ghcr.io/you/dev:1.0 ghcr.io/you/dev:latest
mvz images push "Ubuntu Base" 123456789012.dkr.ecr.eu-west-2.amazonaws.com/ubuntu-base:1 --chunk-size 2048
push sends a stopped VM or a template to one or more tags of one repository. A digest can't be pushed to. In the app, right-click a stopped VM or a template and choose Push to Registry…, then type the references, separated by spaces. Sign in first in Settings → Registries. A push is a task in the Tasks pane, and Cancel, mvz tasks cancel <task-id> or Ctrl-C stops it. Windows VMs can't be pushed yet.
The image is in MacVisor's own format. Tart can't read it. MacVisor pulls it, on this Mac or another.
| Goes | Never goes |
|---|---|
| The boot disk's files as they are, with a linked clone's base | Its name, networks, shared folders, port forwards and USB devices |
| Its platform files: auxiliary storage and hardware model for macOS, EFI variables otherwise | Its MAC address and machine identity |
| The settings that make it the same VM elsewhere | Its other disks, which stay on this Mac |
| Where it came from: a cloud image's catalogue entry and URL, and a base's registry unless it's the one you push to |
Everything on its boot disk goes into the image, so anyone who can pull it can read it. That's every file in the guest, including the accounts, passwords and SSH keys MacVisor set up in it: a cloud-init VM's user data, its SSH host keys and this Mac's authorized key, and a macOS guest's agent token. mvz images push says so before it starts, and so does the push sheet. Push to a private repository, or clean the guest first: in a Linux guest, for example, sudo cloud-init clean --logs --seed, then remove ~/.ssh/authorized_keys, before you shut it down.
Only the boot disk goes. A guest that mounts another disk at startup won't find it on another Mac, and push warns you when the VM has one.
Linked clones go as layers
A linked clone goes as its base and its own changes, as layers. Its base goes whole, with anything deleted from the guest since the base was made: push says so first, unless the base is a template pulled from a registry. A base the registry has already isn't sent again, and one pushed before to another repository on the same registry is mounted from there rather than uploaded. Pushing again with nothing changed sends nothing.
A VM made from a Tart image is a linked clone of a Tart template, so pushing it sends that base too, once per registry, in MacVisor's format.
Pulling works the same way round. When a template here has some of a MacVisor image's disk layers already, MacVisor copies those instead of downloading them, then checks each copy against the image's digest. On the same drive the copy is an APFS clone, which takes no space and no time. One that doesn't match is downloaded. The new template keeps its own copies, so it doesn't depend on the template they came from, and either can be deleted.
Chunks
Each disk file goes in chunks of 512 MiB, or what --chunk-size gives, from 64 to 4096. Use smaller chunks on a slow uplink and larger ones for ECR's layer limit. --concurrency sends 1 to 16 at once (4 by default). push also takes --detach and --json.
Registries on your local network
macOS doesn't let MacVisor's background service have Local Network access, so it reaches a registry on your local network through MacVisor's VM runner, MacVisor VM, with the Local Network permission you gave it in first-run setup. A registry is on the local network when it's at a private, link-local or shared (100.64.0.0/10) address, has a .local name or a single-label name such as nas, or has a name that resolves to one of those, or to an address on one of this Mac's own networks (a VPN's don't count). A registry on this Mac (localhost) is reached directly. If MacVisor VM isn't allowed Local Network access, a pull or push says so.
--insecure speaks plain http. It's only for a registry on this Mac, or one on your local network named by its IP address, a .local name or a single-label name, as macOS allows plain http only to those. Every other registry is reached over HTTPS. images pull, images inspect, images push, images login, and create and clone from a reference take it. To sign in to such a registry, you can also write it as http://host:port. In the app, write http:// before the registry, in Get an Image from a Registry…, Push to Registry… and Settings → Registries. A template pulled over plain http remembers it for Update from Registry.
mvz images push dev localhost:5000/dev:1.0 --insecure
mvz images pull nas.local:5000/lab/ubuntu:1 --insecure
Registries that need care
| Registry | What to know |
|---|---|
| ghcr.io | One upload must finish within about ten minutes. On a slow uplink, push with a smaller --chunk-size. |
| Docker Hub | Anonymous pulls have a low hourly limit. Sign in, even for public images. MacVisor's message says so when you reach it. |
| Amazon ECR | An image can have about 500 layers. A large disk at the default chunk size can need more, so push it with a larger --chunk-size. |
| gitlab.com | Refuses VM image types. MacVisor says the registry doesn't accept VM images. |
License
Pulling and pushing need a license or the free trial: images pull, images push, create and clone from a reference, and Update from Registry. Signing in and out, images login --list and images inspect (Check in the app) don't. Creating and cloning VMs need one as they always have, wherever the image came from.
Moving from Tart
| Tart | MacVisor |
|---|---|
tart clone <image> <name> | mvz clone <image> <name>: pulls the image into a template once, then makes a linked clone of it |
tart pull <image> | mvz images pull <image> |
tart push <vm> <image>… | mvz images push <vm> <image>…, in MacVisor's format, which Tart can't read |
tart login <registry> | mvz images login <registry> |
tart logout <registry> | mvz images logout <registry> |
TART_REGISTRY_* variables | Read as they are, or use MVZ_REGISTRY_* |
A VM in ~/.tart/vms | File → Import… or mvz import. See Import VMs from other apps. |
DeltaSync