mvz is MacVisor's command-line tool. It talks to the same background service as the app, so both see the same library and the same running VMs. Nearly everything you can do in the app, you can script. The exceptions are listed under App-only features.
The tool is also installed as macvisor. Both names run the same command; these docs use mvz.
Install
First-run setup links both names into /usr/local/bin, pointing into the app. The links follow the app when it moves or updates. Linking needs the network helper, which you switch on under System Settings → General → Login Items & Extensions.
Without the helper, link the binary yourself:
sudo mkdir -p /usr/local/bin
sudo ln -sf /Applications/MacVisor.app/Contents/Helpers/macvisor-cli /usr/local/bin/mvz
Once the helper is on, Assistant → Setup Assistant… → Command-Line Tool makes the links for you.
mvz help lists every command. mvz help <command> shows its options and examples.
Shell completion
zsh and fish completion is installed with the command links, into the folders those shells search by default. Nothing in your startup files is changed. The Homebrew cask installs the same scripts into Homebrew's folders.
| Shell | What to do |
|---|---|
| zsh | Works if compinit runs. Oh My Zsh and Prezto run it. A new account has no ~/.zshrc, so add autoload -Uz compinit && compinit to one. |
| fish | Works as installed. |
| bash | Add eval "$(mvz completion bash)" to ~/.bashrc. |
Without the network helper, add eval "$(mvz completion zsh)" to ~/.zshrc, or mvz completion fish | source to ~/.config/fish/config.fish. The scripts complete both mvz and macvisor.
Naming things
A <vm> argument is a VM's name, its id, or the first 4 or more characters of its id.
- Commands that only read, such as
showandip, also accept part of a name. Commands that change a VM, such asstart,set,sendandrm, need the full name or an id. - A selector that matches more than one VM is refused, and the matches are listed.
- Names are unique on a Mac across VMs and templates, ignoring case.
create,cloneandset --namerefuse a name in use and suggest a free one. They also refuse a name that would give a VM another VM's.mvzname: with "Dev VM" on the Mac,dev-vmis refused, since both aredev-vm.mvz.importbrings such a VM in under a free name and says so. Two VMs from an earlier beta that share a.mvzname answer only to their short ids. - Quote names with spaces:
mvz shell 'web server'. - A snapshot is named by its id, the first 4 or more characters of its id, or its exact name.
JSON output
Most commands take --json, including commands that change something (rm, images add, ports add and so on).
mvz list --json
mvz show dev --json
mvz ip dev --wait # prints only the IPv4 address
Exit status
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Failure, including a VM that is busy or still installing macOS |
| 2 | Usage error, including an option the command doesn't take |
| 3 | Not found: no such VM, template, snapshot, network, disk, task or file |
| 4 | The background service can't be reached |
| 5 | Timed out |
| 6 | The VM isn't running, and the command doesn't start it: shell, exec, kubeconfig, wait after an install, restart, suspend, pause, screenshot, trim, tools, send and ip. The message says how to get it running; --start does it first where it's offered |
-
4 means nothing answered. Run
mvz service statusor open the app, which starts the service. See Troubleshooting. -
5 comes from
wait,ip --wait, and fromshellorexecwhen SSH doesn't answer within 2 minutes (10 for a macOS guest). A slow service can still be working on your request, so checkmvz tasksbefore you retry. -
Once connected,
shellandexecexit with the guest command's status, or 255 ifsshfails. Codes 1 to 6 from them mean the connection was never made, unless the guest command itself returned that code. -
ipof a stopped VM exits with status 6 and says so, rather than printing no addresses.
Unknown options are refused with status 2, so start dev --headles fails instead of opening a window.
Confirmation
On a terminal, the commands that delete or replace something ask first: rm, reinstall, snapshots revert, snapshots delete, password --delete, discard-state, flatten, disks remove, images rm and sandbox discard. The question names the VM, snapshot, disk or installer, with its short id, and says what is lost. For example, a running VM is turned off. Only y or yes goes ahead. Any other answer exits with status 1 and changes nothing.
--yes(or-y) skips the question.- Nothing is asked unless standard input and standard error are both a terminal. In a script, a pipe or an AI agent's shell:
rm,snapshots revert,discard-stateandpassword --deletego ahead, as before.rmmoves the VM to the Trash, and a revert can take a safety snapshot;reinstall,snapshots delete,flatten,disks remove,images rmandsandbox discard, which can't be undone, need--yes. Without it they exit with status 2, "add --yes to go ahead", and change nothing. Scripts written for earlier versions must add it.
Pass --yes in a script you also run from Terminal. The MCP server passes it where it acts for an agent.
A VM or template protected from deletion is refused by rm, reinstall, disks remove, images rm and sandbox discard before anything is asked, with exit status 1. --yes doesn't get past it.
License
Without a license, create, clone, import, images pull, images push and images add are refused. Everything else works, and existing VMs keep running. The regular license check (every few days) runs whether or not the app is open, so a Mac you only reach over SSH stays licensed.
mvz license # edition, key, last check
mvz license trial # start the free 30-day trial (once per Mac)
mvz license activate # asks for the key in a terminal
echo "$KEY" | mvz license activate - # read the key from standard input
mvz license activate <key> --move # move a key that is active on another Mac
mvz license refresh # check with the licensing server now
mvz license deactivate [--yes] # release this Mac so the key can move
The app starts the trial by itself once its License Agreement is accepted. license trial starts it on a Mac you only reach over SSH, which never shows the agreement.
Create VMs
From a Linux cloud image
mvz create <image> <name> [options]
<image> is a catalogue entry (ubuntu, debian-13, fedora; mvz images lists them), a cloud-image template, a disk image file (qcow2, raw, or .xz), or an https URL. The image is downloaded once and kept as a template. The VM is a linked clone of it, set up by cloud-init on first boot.
| Option | Effect |
|---|---|
--cpus <n> | Virtual CPUs (default 4) |
--memory <GiB> | Memory (default 4) |
--disk <GiB> | Disk size, never smaller than the image (default 64) |
--runtime <name> | containerd, docker, podman, k3s, or none (default) |
--home | Share your home folder read-only at the same path |
--ssh-key <file> | Authorise this public key. Repeatable. Default: every key in ~/.ssh and your SSH agent. |
--no-ssh-keys | Authorise no keys |
--user-data <file> | Your own #cloud-config or script, applied after MacVisor's |
--network <network|nat> | A custom network by name or id, or nat (default: Default MacVisor Network) |
--no-forward | Don't forward guest ports to the Mac automatically |
--no-agent | Don't install the MacVisor Agent |
--no-cloud-init | An appliance: boot the image as it is, with no cloud-init seed, account, SSH keys or agent |
--cloud-init | Set the VM up with cloud-init, from a template that makes appliances |
--password-file <file|-> | FreeBSD: the account's password for its console, from a file or standard input (-). MacVisor deletes its copy once setup is done. Without it, MacVisor makes a random one, which stays. Refused for Linux images and with --no-cloud-init. |
--os linux|freebsd | A disk image or URL: the system it is. Without it, MacVisor goes by the catalogue, then the file's name, then its partitions. The template remembers it. A catalogue image can't be made the other system. Given for a template already in the library, --os changes that template's system for later VMs; VMs made from it before are unchanged, and create, images pull and New VM say so (templateOS in --json). |
--rosetta, --no-rosetta | Run x86-64 Linux binaries with Rosetta (default: on when installed) |
--nested, --no-nested | Nested virtualization (default: on, on M3 or later) |
--balloon, --no-balloon | Memory balloon device (default: on) |
--location <location> | Storage location by name or id |
mvz create ubuntu dev
mvz create ubuntu dev --runtime containerd --home
mvz create fedora-44 build --cpus 8 --memory 16 --runtime podman
mvz create debian dev2 --user-data cloud.yaml
mvz create freebsd bsd
mvz create ~/Downloads/pfSense-CE-2.8.0.img fw --no-cloud-init --os freebsd
Things to know:
- Nothing is installed or shared unless you ask. Unlike the New VM form,
createdoesn't remember your last choices. - Not every runtime is available on every image.
createrefuses an unavailable one and lists what the image offers. See Runtimes by distribution. - FreeBSD (
freebsd,freebsd-current) is used over SSH and its console, and MacVisor installs its agent over SSH after the first boot (not with--no-agent).createrefuses--runtimeand--home, and turns Rosetta and automatic port forwarding off. See FreeBSD guests. --no-cloud-initmakes an appliance from a router, firewall or other image without cloud-init: no seed, account or SSH keys. A Linux appliance gets the guest tools disc at each start, for an agent you install yourself, and a QEMU guest agent port. It refuses--user,--ssh-key,--user-data,--runtime,--homeand--rosetta. The template made from a file or URL remembers the choice, and--cloud-initoverrides it. A service older than the CLI makes a cloud-init VM instead, andcreatesays so. See Appliances.createdoesn't start the VM unless you add--start(without a window; a Linux VM from an ISO with its window, to install in). Or usestartandwait. A macOS or Windows VM starts its install as it's made, so--startdoesn't apply to it:wait <vm> --startstarts it once the install is done.- A template is taken by its whole name or id. A catalogue name (
ubuntu,debianand so on) always means the catalogue image, also when a template's name contains it. - A Linux VM's account has no password, so you sign in with an SSH key. A FreeBSD VM's has one for its console, random unless
--password-filegives it. If you have no key,createoffers to make~/.ssh/id_ed25519. If you decline, or run without a terminal, the VM gets MacVisor's own key, which onlymvz shellandmvz execuse. - Ubuntu Desktop is chosen in the New VM form only. For a VM made with it,
mvz password <vm>prints the account's password for the desktop's lock screen and admin prompts.
From a macOS installer
mvz create <macos-installer|file.ipsw> <name> [--cpus <n>] [--memory <GiB>] [--disk <GiB>] [--user <name>] [--ssh] [--network <network>]
Use a catalogue installer (macos-27, macos-26, macos-15, macos-14, or macos-latest for the newest this Mac supports) or a local .ipsw. macos installs the newest macOS this Mac can install, as New VM does: 27.2 on a Mac running 27.2, otherwise an older one. An installer the catalogue no longer lists but this Mac keeps, such as 27.0.1, goes by its version: mvz create macos-27.0.1 <name>. The installer is downloaded from Apple once. The install takes 20 to 30 minutes and shows in mvz tasks. macOS VMs get 8 GiB of memory by default. Give them 4 or more CPUs.
create returns once the install starts. Use mvz wait <vm> --ssh --start to follow it and start the VM once it's installed (see Waiting for a VM).
macOS 27 guests skip Setup Assistant. Older guests go through it by hand.
| Option | Effect |
|---|---|
--user <name> | Account that automatic setup creates (default macvisor) |
--password-file <file> | Read the account's password from a file (- for standard input). MacVisor deletes its copy once setup is done. Without it, MacVisor makes a random password, which stays; mvz password <vm> prints it |
--ssh | Turn on Remote Login, for shell and exec |
--no-autologin | Don't sign in automatically |
--no-setup | Skip automatic setup and use Setup Assistant |
With --ssh, MacVisor installs the agent and authorises your SSH keys after the first boot. This needs the Local Network permission.
mvz create macos-latest ci --user builder --ssh
mvz create ~/Downloads/UniversalMac_15.6.1_24G90_Restore.ipsw sequoia
From a Windows 11 ISO
mvz create <windows.iso> <name> [--cpus <n>] [--memory <GiB>] [--disk <GiB>] [--user <name>] [--password-file <file>] [--no-autologin] [--ssh] [--winrm] [--visual-effects reduced|full] [--no-secure-boot] [--no-tpm] [--network <network>]
Microsoft's Windows 11 on ARM ISO installs unattended in about 10 minutes. The install is a task you can cancel. Remote Desktop and the MacVisor Agent are set up for you, and the VM has UEFI Secure Boot and a TPM 2.0. The setup accepts Microsoft's license terms for you and leaves Windows unactivated, for your own license.
| Option | Effect |
|---|---|
--user <name> | Account name (default macvisor) |
--password-file <file> | Read the password from a file (- for standard input). MacVisor deletes its copy once Windows has installed. Without it, MacVisor makes a random password, which stays; mvz password <vm> prints it |
--no-autologin | Don't sign in automatically |
--ssh | Add OpenSSH Server with your keys, so shell and exec work (PowerShell) |
--winrm | Turn on Windows Remote Management |
--backup on|off | At once. Scheduled backups, off by default. Set on its own, apart from the other options. |
--backup-to <folder> | At once, and turns backups on. A local, external or network folder outside the storage locations; each VM's backups go in a folder of their own in it. |
--backup-schedule daily|weekly|<day> | Daily (the default), weekly on Sunday, or weekly on the day you name (monday, tue…). |
--backup-at HH:MM | The time, in this Mac's time zone (03 unless set). |
--backup-keep <n> | How many backups to keep, the newest (7 unless set). |
--visual-effects reduced|full | reduced (the default) turns Windows' transparency and animations off at the first sign-in, since Windows draws them with the Mac's CPU; full leaves Windows' defaults. set --visual-effects changes it later (details) |
--no-secure-boot | Make it without UEFI Secure Boot, which it has otherwise. set --secure-boot changes it later (details) |
--no-tpm | Make it without a TPM, which it has otherwise. set --tpm changes it later |
mvz create ~/Downloads/Win11_26H2_English_Arm64.iso win11 --ssh
An .iso without --os linux is taken as a Windows ISO.
Every new Windows VM gets sound in its window: MacVisor's audio device, with speakers and a microphone. --no-sound is no longer an option: it exits with status 2 and names mvz set <vm> --sound off, which turns sound off once the VM is made (details).
See Windows 11 guests.
From a Linux ISO
mvz create <linux.iso> <name> --os linux [--cpus <n>] [--memory <GiB>] [--disk <GiB>] [--disk-format asif|raw] [--sector-size 512|4096|4k] [--network <network|nat>] [--location <location>] [--nested|--no-nested]
Makes a Linux VM with a blank disk and the ISO attached for its first boot. Start it and install Linux in its window, as with Linux → Installer ISO in the app. The disk is ASIF unless --disk-format raw: a plain image other tools can read, which can't be linked-cloned, and whose copy to another drive needs its full size free. --sector-size is 512 (the default, what most installers and disk tools expect) or 4096 (4k, native, matching APFS); a raw disk is always 512. The VM joins the Default MacVisor Network unless --network says otherwise, and create says which network it's on (network in --json); it also says the sector size the disk was made with, and warns when an older background service didn't make what you asked. FreeBSD isn't installed from an ISO.
mvz create ~/Downloads/archlinux-aarch64.iso arch --os linux --disk-format raw
Erase and install again
mvz reinstall <vm> <file.ipsw|windows.iso> [--safety-snapshot | --no-safety-snapshot] [--yes]
Erases a macOS or Windows VM's disk and installs the system again, for a VM whose install failed or was cancelled and that hasn't started since. A VM whose install succeeded is refused, since it may hold your work: to start over, create a new VM and delete the old one. The VM must be shut down, with no suspended session, and is named in full or by its id. A Windows VM keeps the account and options it was created with, and its setup again accepts Microsoft's license terms for you. It doesn't update the system. It is refused for a VM that shares its disk with linked clones, and for one protected from deletion. A macOS VM also needs at least 2 CPUs, 4 GB of memory and a 32 GB disk, and no more than one other macOS VM running or installing, since a Mac runs two macOS VMs at a time.
On a terminal it asks first and takes a snapshot of the disk before the erase, named "Before installing macOS" (or Windows), as the app's Take a Snapshot, Then Erase does. These refusals come before the question, so no snapshot is taken for nothing. If the snapshot fails, nothing is erased. --yes doesn't ask and, as in a script, takes no snapshot; --no-safety-snapshot skips it, and --safety-snapshot takes it with --yes or in a script.
From a template
mvz template <vm> on # make a shut-down VM a template
mvz create <template> <name> # copy it
mvz template <vm> off # make it runnable again
A template you made from a VM is copied as it is, and create options don't apply. A suspended VM can't become a template. Resume and shut it down, or discard its saved state, first. See Create VMs and templates.
Images and installers
mvz images # templates, cloud images and macOS installers
mvz images pull <image> [--os linux|freebsd] # download ahead of first use
mvz images add <file> # qcow2, raw, .xz or .ipsw you already have
mvz images rm <template|installer> [--yes]
pullalso fetches a newer vendor build for "latest" entries such asubuntu.createuses the template you already have.rmmoves a template to the Trash. It is refused while VMs are made from it, and for a template protected from deletion. Given an installer id such asmacos-26, it trashes the IPSW.- An installer you
pulloraddstays until you remove it. Onecreatedownloaded is trashed when a newer build of its release arrives, such as 27.0.2 for 27.0.1. A newer release, such as 27.2, doesn't replace it. - A local
.ipswyoucreatefrom is added to the installers when it's on the same disk as them, as a copy that takes no extra space. From another disk it's read where it is. --ossays which system a disk image or URL is, as forcreate. A pull that resumes after an interruption keeps it.- Interrupted or cancelled downloads resume on the next
pullorcreate.
Registry images
mvz images pull <registry>/<repository>[:<tag>] [--name <template>] [--location <location>] [--concurrency <n>] [--insecure] [--detach]
mvz images inspect <registry>/<repository>[:<tag>] [--tags] [--insecure]
mvz images push <vm|template> <registry>/<repository>:<tag>... [--chunk-size <MiB>] [--concurrency <n>] [--insecure] [--detach]
mvz images login <registry> [--username <user>] [--password-stdin] [--insecure]
mvz images login --list
mvz images logout <registry>
mvz images source <template> [<reference>] [--insecure]
mvz create <registry>/<repository>[:<tag>] <name> [--location <location>] [--network <name>] [--forward] [--insecure]
mvz clone <registry>/<repository>[:<tag>] <name> [--location <location>] [--insecure]
mvz images inspect ghcr.io/cirruslabs/debian --tags
mvz images pull ghcr.io/cirruslabs/macos-golden-gate-base:latest
mvz clone ghcr.io/cirruslabs/macos-golden-gate-base mac
echo "$GHCR_TOKEN" | mvz images login ghcr.io --username me --password-stdin
mvz images push dev ghcr.io/you/dev:1.0 ghcr.io/you/dev:latest
- A reference names its registry (
ghcr.io/…,docker.io/…,localhost:5000/…), which keeps it apart from a catalogue id, a URL or a file. The tag islatestunless you give one. pullmakes a template from a Tart image or a MacVisor one. A raw disk becomes ASIF as it downloads. The template is named after the repository, with its tag and where it's from (debian (cirruslabs)), unless--namegives another. Pulling a tag whose image is here already does nothing.createandclonepull the image first if it isn't here, then make the VM from its template.inspectsays what an image is without pulling it.--tagslists the repository's tags.sourceshows where a template came from; with a reference, it changes the repository and tag Update from Registry follows. A digest is refused.pushsends a stopped VM or a template to one or more tags of one repository, in MacVisor's own format, which Tart can't read. Everything on its boot disk goes, including the accounts, passwords and SSH keys MacVisor set up in the guest, andpushsays so before it starts. Its name, networks, shared folders and other disks never go. A linked clone's base is sent once, and later pushes skip it.--chunk-sizeis 64 to 4096 MiB (512 by default). Windows VMs can't be pushed yet.--concurrencymoves 1 to 16 chunks at once (4 by default). Ctrl-C cancels a pull or push, and--detachreturns once it has started. A cancelled pull carries on at the next one.loginchecks the sign-in with the registry, then keeps it in this Mac's Keychain. The password or token comes from standard input or a prompt, never the command line.mvzalso passes onMVZ_REGISTRY_*orTART_REGISTRY_*variables, which come before the Keychain sign-in, and Docker's credentials, which come after it.--insecurespeaks plain http, only to a registry on this Mac, or one on your local network named by its IP address, a.localname or a single-label name. A sign-in to one can also be writtenhttp://host:port.
See Registry images (OCI) for what a pulled template is, Tart's images, credentials, layers, and registries that need care.
Storage locations
mvz locations # list storage locations
mvz move <vm> <location> # move a VM to another location
create, images pull, clone and import take --location. move suspends a running VM, moves it and resumes it, which needs the Mac's screen unlocked. See Storage and sharing.
Run and control VMs
| Command | What it does |
|---|---|
mvz list | List VMs (alias ls) |
mvz show <vm> | Show a VM's details (alias info) |
mvz start <vm> [--headless] | Start, without a window if --headless |
mvz start <vm> --recovery | Start a macOS VM in recoveryOS |
mvz stop <vm> | Ask the guest to shut down |
mvz poweroff <vm> | Turn it off at once |
mvz restart <vm> | Restart a running VM (one that isn't is refused with status 6, with what to do) |
mvz pause <vm> | Freeze it in memory |
mvz suspend <vm> | Save its state to disk and stop |
mvz resume <vm> | Resume a paused or suspended VM |
mvz discard-state <vm> | Drop a suspended VM's saved state |
mvz open <vm> | Show a running VM's window |
mvz autostart <vm> on|off [--window|--background] | Start the VM when you log in, in the background or with its window open |
mvz ha <vm> on|off | Start the VM again after a crash or a failed start |
mvz ha <vm> on --watchdog [normal|low] | Also restart it when its guest stops responding: VM Watchdog (--no-watchdog turns that off) |
mvz hooks <vm> | List, set, remove and test the VM's hooks and health check |
mvz events [<vm>] [--follow] | What happened to VMs lately, or as it happens |
mvz protect <vm> [on|off] | Show or set whether the VM or template is protected from deletion |
mvz rm <vm> | Move a VM or template to the Trash (alias delete) |
Several VMs at once. start, stop, poweroff, restart, suspend, pause, resume, open, rm, flatten and discard-state take several VMs, mvz stop web db, and act on each in turn, printing a line for each. The exit status is non-zero if any of them failed, and --json prints an array with one entry per VM. With one VM, output and exit status are as before.
listalso shows the VMs being made, while their installer, drivers or cloud image download or their disk image converts, with the statedownloadingorpreparingand how far along they are:mac27 (34%).--jsonlists made VMs only;mvz tasks --jsonhas the downloads. See While a VM is being made.showof any running VM has a Memory in use row, as the Overview's Memory card:5.2 GB used in the VM, of 8 GB (pressure normal); 8.1 GB held on this Macwhen its agent reports the guest's memory, else what the VM holds on this Mac. In--json:guestMemory(usedBytes,totalBytes,pressure) andhostMemoryBytes.showof a running Linux or FreeBSD VM has a Guest tools row (running,limited,not detectedand so on), a Guest tools disc row, and a QEMU Guest Agent row with its version when one answers, followed by Guest OS, Hostname and Kernel while the status is limited. In--json:guestToolsStatus,toolsDiscandqemuGuestAgent. See QEMU guest agent.- A FreeBSD VM's OS is
freebsdinlistandshow, and"os": "freebsd"in--json("guestOS": "freebsd"is still there too). Earlier versions'--jsonsaid"os": "linux". - A FreeBSD-based appliance (made with
create --no-cloud-init):stopandrestartgo through its agent or QEMU guest agent if one answers, else press its power button. If it doesn't act on it, shut it down from its own interface, or usepoweroff. See Appliances. - Resuming needs an unlocked screen. macOS restores a saved session only while you are logged in at the Mac with the screen unlocked. Over SSH with the screen locked,
startandresumesay so and keep the session. If a session can't be restored for another reason,showsays why, andstart --discard-stateboots from the disk instead. See Run and control VMs. autostart onstarts the VM at each login, whether or not the app opens. Whether it boots fresh or resumes depends onmvz service at-logout(see Housekeeping).autostart on --windowopens the VM's window when it starts at login;--backgroundgoes back to the default. Either one alone changes only that choice.ha onhas the background service start the VM again when it crashes, stops with an error or fails to start: 5 s after the first stop, then 15, 30, 60, 120 and 300 s between tries, giving up after ten restarts within an hour. A stop you ask for, frommvz, the app or inside the guest, is never followed by a restart.showprintsHigh availability: on, andtaskshistory has aRestarted by high availabilityevent for each restart. Templates can't have it. See High availability.ha on --watchdogturns on VM Watchdog, which needs MacVisor's guest agent in the VM, or a QEMU guest agent where MacVisor's can't run (an appliance such as Home Assistant OS): with normal sensitivity, the default, MacVisor restarts the VM once its guest has stopped responding for 10 minutes, as the agent reports it, at most 3 times a day; withlow, after 30 minutes, once a day. Never in the first 10 (low: 20) minutes after a start, nor while you're using its window, and one VM at a time.--no-watchdogturns it off.--monitoringand--no-monitoring, its earlier names, still work. It acts only while high availability is on, soha <vm> off --watchdogis refused.ha <vm>says what it's doing, andshowhas VM Watchdog and Guest status rows. In--json:vmMonitoring(enabled,sensitivity) and, while the VM runs,monitoringStatus.restartrestarts only a running VM. One that isn't is refused with status 6 and what to do instead:starta stopped one,resumea suspended one, wait for an install to finish (wait --start), or see why one failed (show). If MacVisor can't read the VM's state in time,restartrefuses too, rather than risk starting a VM that wasn't running.rmpowers off a running VM first. It is refused while other VMs read its disk, so delete linked clones before their source, and for a VM protected from deletion. With several VMs, one that's refused doesn't stop the others. It is also refused on a drive with no Trash, such as a network share:mvz movethe VM to another location first, or shut it down and delete its folder in Finder.
Protect from deletion
mvz protect web on # nothing can move it to the Trash or erase it
mvz protect web # is it protected?
mvz protect web off # turn it off before you delete it
While it's on, MacVisor refuses to move the VM or template to the Trash or to erase it, however it's asked: rm (--yes or not), images rm, reinstall, disks remove, sandbox discard, Move to Trash in the app, or an AI agent's vm_delete. The same switch is in VM Settings → General. Agents can't turn it on or off. It goes with the VM when it's exported, backed up, imported, restored or moved. A clone or copy, and a VM made from a protected template, starts without it.
showhas a Deletion protection: on line while it's on.show --json,list --jsonandprotect --jsonhavedeletionProtected.- Turning it on is refused while the background service still runs an older build after an update, which wouldn't enforce it: open MacVisor or run
mvz service restart, then try again. Turning it off always works.
Waiting for a VM
mvz wait <vm> [--ssh] [--start] [--timeout <seconds>]
mvz ip <vm> [--wait]
wait returns when the VM is running, its agent answers and, for a cloud-image VM, cloud-init has finished. For a macOS guest, or a VM made with --no-agent, it returns when SSH answers; for a FreeBSD guest, when SSH answers and its first-boot setup has finished (2 to 5 minutes the first time). An appliance has nothing to set up: wait returns once it runs, and with --ssh once port 22 answers on its first adapter's address. A Linux or FreeBSD VM made without cloud-init (installed from an ISO, or imported) whose QEMU guest agent answers is ready without MacVisor's agent: "deb is ready: its QEMU guest agent answers (MacVisor's agent isn't connected)." With --ssh it waits for SSH too. wait doesn't start the VM. It exits with status 5 after --timeout (default 600 seconds). During a cloud-image VM's first boot it prints each setup step as it comes, with the time left, and show has it as a Setup step row: see First-boot progress.
--sshalso waits for SSH. If SSH answers but the agent hasn't connected after 3 minutes,waitreturns anyway and says so. cloud-init may still be running, so useexec --waitif you depend on it.- A VM installing macOS or Windows is followed through its install. When it ends, the VM is stopped: with
--start,waitstarts it without a window and waits as usual; without, it says so and exits with status 6. It exits with status 1 if the install fails. A--timeoutyou give covers the install too. --startalso starts a stopped VM (or resumes a suspended or paused one) before waiting. With--start,wait,shell,execandkubeconfigneed the VM's full name or id (or the start of its id), not a partial name, so a typo can't start another VM.ip --waitwaits up to 10 minutes for a running VM's IPv4 address and prints only that. A VM that is stopped, suspended or paused fails within seconds.
Commands that start a VM
Only the power commands start a VM by themselves (start, resume, restart of a running VM), along with what you set up to do it for you: autostart at login, high availability and VM Watchdog. Every other command acts on the VM as it is: one that needs it running, and finds it stopped, suspended or paused, says so with the command that gets it running, and exits with status 6.
Where starting it first is useful, --start asks for it:
| Command | With --start |
|---|---|
mvz shell <vm> --start, mvz exec <vm> --start -- <command> | A stopped VM is started without a window, a suspended or paused one resumed, then it connects. |
mvz kubeconfig <vm> --start | The same, then it reads the cluster's config. |
mvz wait <vm> --start | A VM that isn't running is started first, and a macOS or Windows VM once its install has finished. |
mvz create <image> <name> --start | The new VM is started once it's made (not for macOS or Windows, which start their install). |
AI sandboxes are the exception: a sandbox is started when you use it (sandbox run, attach, shell, exec, diff, apply), as that's what it's for.
Nothing else starts a VM: open, ip, ports, screenshot, rdp, console, send, tools, usb attach and usb detach need it running, a health check never starts one, and plain ssh <vm>.mvz through MacVisor's SSH config says the VM isn't running. Over MCP, agents' tools never start one of your VMs (except vm_power); a sandbox's tools start the sandbox. In the app, Open in Terminal starts the VM first, as it always has.
Console, screenshot, NVRAM and Remote Desktop
mvz console <vm> [--raw] # serial console; Control-] detaches
mvz screenshot <vm> <file.jpg>
mvz nvram <vm> [set <name> <value> | rm <name>]
mvz rdp <vm> [--print]
consoleneeds Serial Console on in VM Settings → Advanced. Text, colours and cursor movement reach your terminal, so login prompts,viandnanowork. The guest's graphics (Kitty, Sixel, iTerm images), clipboard writes (OSC 52), file transfers, links, notifications and window titles are dropped.--rawpasses the guest's output as it is, for a guest whose console uses graphics or other terminal features you trust. A FreeBSD VM always has it. A new one shows a login prompt for your account (mvz password <vm>); see FreeBSD VMs made before for an older one. Up to 8 terminals can attach at once: each sees the guest's output, and what any of them types goes to the guest. A ninth is told the console is busy. See Display and console.screenshotsaves a preview of a running VM's display, a JPEG at most 640 pixels wide (the picture the Overview shows), and prints the size it saved. A Windows VM on Virtio-MVZ is captured with or without a window. Any other VM needs its window shown since it started (mvz open).nvramdoesn't work on macOS 27, so MacVisor says so and changes nothing. Set boot-args from Terminal in recoveryOS instead (mvz start <vm> --recovery, thennvram boot-args=-v).rdpopens a running Windows VM in Microsoft's Windows App (Remote Desktop), for a session from another computer; the VM's own window (mvz open) is the everyday display.--printwrites the.rdpfile and prints its path for another client. See Remote Desktop.
Hooks, health checks and events
mvz hooks <vm> [--json] # list
mvz hooks <vm> set started|stopped '<command>' # run on this Mac, as you, after the VM starts or stops
mvz hooks <vm> set shutdown '<command>' # runs instead of MacVisor's own shutdown
mvz hooks <vm> remove <hook>
mvz hooks <vm> test <hook> [--yes] [--json]
mvz hooks <vm> check set ['<command>'] [--in-guest|--on-mac] [--interval N] [--restart|--no-restart] [--on-fail '<command>'|--no-on-fail]
mvz hooks <vm> check [test|remove]
mvz events [<vm>] [--follow] [--json]
- Hooks run on this Mac, as you, from
/bin/zsh -c, with the event inMVZ_variables and as JSON on standard input. A VM has four:shutdown,started,stoppedandcheck-failed(the health check's--on-fail); any other name is refused. Put the command in single quotes, so$MVZ_VM_NAMEis expanded when the hook runs. See VM hooks. test shutdownruns the shutdown command for real, so while the VM runs it needs--yes.testexits 1 when the hook failed.check setsets the VM's health check, on this Mac or with--in-guestin the guest over SSH, every 30 to 3600 seconds (60 by default). With--restart, it restarts the VM once it has failed for 20 minutes (10 in the guest), at most once a day, with or without high availability.--on-fail "<command>"runs a command on this Mac then, and--no-on-failremoves it. On a VM that has a check, it changes only what you give and keeps the rest:mvz hooks web check set --interval 120keeps the command and the restart. On a VM with none, it needs the command, and what you leave out is the default (on this Mac, every 60 seconds, no restart).--in-guestwith--on-mac, or--restartwith--no-restart, is refused. See Health checks.eventsprints the last 200 events the background service remembers, or with--follownew ones as they happen.--jsonprints one JSON object per line.stopfrom a VM's own shutdown command doesn't cancel the Restart it's part of: it says"web" is already shutting down: its shutdown command is running.showhas Hooks and Health check rows, andhooks,hookCheckandhookCheckStatusin--json.
Work inside a VM
Shell and exec
mvz shell <vm> [--start] [--workdir <dir>] [--wait] [--user <name>] [<command>...]
mvz exec <vm> [--start] [--workdir <dir>] [--wait] [--user <name>] [--] <command>...
shell (alias ssh) opens SSH with your keys and the VM's pinned host key. It connects as soon as SSH answers. A VM that isn't running is refused with exit status 6 and the command that starts it; --start starts it without a window (or resumes a suspended or paused one) first. The shell opens in the guest folder that matches your current one, if that folder is shared. exec runs one command. Put -- before a command that has its own options.
exec takes the command as ssh does. One quoted argument is run as a command line by the guest's shell, so pipes and redirects work: mvz exec mac "/usr/bin/log show --last 1h | tail". Several arguments are passed word for word, each quoted, so mvz exec dev -- ls "My Dir" keeps My Dir one word.
| Option | Effect |
|---|---|
--workdir <dir> | Start in, or run in, this guest folder |
--wait | Wait for cloud-init to finish first |
--user <name> | Sign in as this guest account |
mvz shell dev
mvz exec dev -- nerdctl run --rm alpine uname -m
mvz exec dev --workdir /tmp -- ls -la
mvz exec mac "/usr/bin/log show --last 10m | tail"
- The connection goes through the MacVisor Agent, not the network, so it needs no Local Network permission. If the agent isn't answering, it falls back to the VM's IP address, and your terminal app's Local Network permission applies.
- macOS guests need Remote Login (
--sshat creation, or the guest's Sharing settings). On a guest MacVisor didn't set up, the firstshellasks for the account's password once and authorises your key. While macOS is still installing,shellsays so and exits with status 1. - Windows guests made with
--sshopen PowerShell. Pass a command with pipes or switches as one argument:mvz exec win11 -- "Get-Service sshd | Select-Object Status". - Keys.
shellandexecoffer the keys the VM was made with, at most five, so a Mac with many keys still signs in. When MacVisor doesn't know them (a VM made with--no-ssh-keysor on another Mac, or keys added by hand), they offer MacVisor's key and the first of yours, and say how to use the right one withssh -i. - Appliances (made with
create --no-cloud-init) have no MacVisor account or agent, soshellandexecrefuse them. Use the appliance's own web UI or console.
SSH and editors by name
mvz ssh-config --install # or --uninstall, --print
MacVisor keeps ~/.macvisor/ssh/config current for every cloud-image and macOS VM, with host keys pinned. --install adds one Include line to ~/.ssh/config. After that, ssh dev.mvz, scp and VS Code Remote-SSH work. A symlinked ~/.ssh/config stays a symlink. If MacVisor can't safely edit the file, it tells you which line to add yourself.
<vm>.mvz names resolve on the Mac without this. See Networking.
Account password
mvz password <vm> # print it
mvz password <vm> --delete [--yes] [--json]
password prints the password of the setup account: the one a macOS VM's automatic setup or a Windows VM's install made, or an Ubuntu Desktop or FreeBSD VM's. It's for the login window, sudo, admin prompts, a Windows VM's first Remote Desktop sign-in and a FreeBSD VM's console. SSH into a Windows or FreeBSD VM takes keys only, and into a macOS VM once its agent is set up.
--delete deletes MacVisor's copy, as Delete Password… in the app does. The account keeps its password. After that, password says it was deleted, and the app's Copy, Show and Type Password stop working for the VM. It can't be undone, and it's refused until MacVisor no longer needs the password: until a macOS VM's automatic setup, a Windows VM's install or an Ubuntu Desktop or FreeBSD VM's setup has finished, and while a snapshot taken before then exists. A password from --password-file is deleted by itself once setup is done. On a terminal it asks first; --yes doesn't.
Copy files in
mvz send <vm> <path>...
Copies files or folders into the VM through the agent. They land in ~/Transfers in a macOS guest, on the desktop or in the home folder in a Linux guest, and in the home folder in a FreeBSD guest. A Linux or FreeBSD guest with several accounts, where none was named when its agent was installed, gets them in /root/MacVisor Transfers, which only root can open: install the agent again with --user <name> to name one. For ongoing access, use a shared folder.
Guest tools disc
mvz tools status <vm> [--json]
mvz tools attach <vm> [--json]
mvz tools eject <vm> [--json]
Attaches or ejects the guest tools disc in a running VM, for this run only, as Attach Guest Tools on the Overview does. The VM's settings don't change: whether each start attaches the disc is in VM Settings → Guest Tools. Any Linux, macOS or FreeBSD VM can have it, appliances included, but not a Windows VM. A Linux VM's disc from the start can't be ejected until it shuts down. A disc attached while the VM runs is ejected before a suspend or a live snapshot, and put back after the snapshot. mvz show has a Guest tools disc row while the VM runs. See The guest tools disc.
USB devices
mvz usb list <vm> [--json]
mvz usb attach <vm> <device> [--once] [--json]
mvz usb detach <vm> <device> [--once] [--json]
mvz usb remember <vm> <device> [--json]
mvz usb forget <vm> <device> [--json]
Passes a USB device plugged into the Mac to the VM. attach also remembers it, so it's attached whenever the VM runs and it's plugged in, window or not; detach forgets it. With --once, attach is for this run and detach leaves it remembered. <device> is a name, vid:pid or vid:pid:serial, as list shows them. list, remember and forget work while the VM is stopped; attach or detach of a device on a stopped VM exits with status 1. attach waits up to a minute, as macOS may be asking whether to allow the device. A suspend or a live snapshot detaches the devices attached to the VM and attaches them again once it runs: the guest sees them unplugged and plugged in again, and remembered devices stay remembered. See USB devices.
Disk images as USB drives
mvz usb attach <vm> --image <path> [--json]
mvz usb detach <vm> --image <path|id> [--json]
mvz usb list <vm> [--json]
attach --image puts an ISO or a disk image file on the Mac in the running VM as a USB drive, read-only, until the VM stops, and prints its id. detach --image ejects it, by its path or that id. The id changes when a suspend or a live snapshot detaches the image and attaches it again; its path always finds it. list shows the images attached, with their files and ids, after the Mac's USB devices (diskImages in --json). A relative path is from the folder you're in. A VM that isn't running is refused with exit status 6.
Raw images (.iso, .img, .cdr) and ASIF are attached. A QCOW2, VMDK, VHD(X), VDI, sparse or compressed image, or a .dmg in Apple's compressed format, is refused with the command that makes a raw copy, and so is any file in a VM's or template's package. See Disk images on a running VM. To attach an image at every start, add it in VM Settings → Storage.
mvz usb attach dev --image ~/ISOs/ubuntu-26.04-live-server-arm64.iso
mvz usb detach dev --image ~/ISOs/ubuntu-26.04-live-server-arm64.iso
Port forwards
mvz ports list <vm>
mvz ports add <vm> --host-port <port> --guest-port <port> [--udp] [--host <address>] [--name <name>]
mvz ports remove <vm> <forward-id|host-port>
A VM's forwards are for this Mac only. They listen on 127.0.0.1, and on ::1 too so that localhost reaches the VM; --host ::1 listens on ::1 alone. MacVisor's guest agent carries each connection to the guest's localhost, so the network firewall doesn't apply to them, and they apply immediately. Any other --host, such as 0.0.0.0 or a LAN address, is refused: for other devices, use a custom network's port forwarding or a bridged network. Linux cloud-image VMs also forward the ports the guest listens on automatically. See Port forwarding.
mvz ports add dev --host-port 8080 --guest-port 80 --name web
mvz ports add dev --host-port 5353 --guest-port 53 --udp
Docker, Podman and Kubernetes
mvz docker <vm> [--use | --remove]
mvz kubeconfig <vm> [--save] [--start]
dockercreates a docker context namedmacvisor-<name>for a Docker or Podman VM. The VM's Docker API is forwarded to a socket on the Mac while the VM runs.createmakes the context for you if thedockercommand is installed. For a VM made in the app, runmvz docker <vm>.--usemakes it the current context, and--removedeletes it.kubeconfigprints a k3s VM's kubeconfig.--savewrites it to~/.kube/macvisor-<name>.yaml. It needs the VM running (--startstarts or resumes it first) and waits for setup. The API server is forwarded to127.0.0.1:6443, and only one VM can hold that port, so run one k3s VM at a time.
mvz create ubuntu web --runtime docker
mvz start web --headless && mvz wait web
docker --context macvisor-web ps
mvz create ubuntu k8s --runtime k3s --memory 8
mvz kubeconfig k8s --save --start
KUBECONFIG=~/.kube/macvisor-k8s.yaml kubectl get nodes
Change, copy and move VMs
Settings
mvz set <vm> # show the current settings
mvz set <vm> [--name <name>] [--cpus <n>] [--memory <GiB>] [--disk <GiB>] [--network <network>]
[--iso <file|none>] [--nested on|off] [--balloon on|off] [--balloon-target <GiB>]
[--clipboard both|to-vm|from-vm|off] [--display macvisor|apple]
[--density automatic|retina|standard] [--visual-effects reduced|full]
[--sound on|off] [--microphone on|off]
[--agents off|view|control] [--cloud-init on|off] [--qemu-ga on|off]
[--secure-boot on|off] [--tpm on|off]
[--auto-snapshots off|hourly|daily|weekly] [--auto-snapshots-keep <n>] [--yes] [--discard-state]
mvz set <vm> [--backup on|off] [--backup-to <folder>] [--backup-schedule daily|weekly|<day>]
[--backup-at HH:MM] [--backup-keep <n>]
| Option | When it applies |
|---|---|
--cpus, --memory, --network, --nested, --balloon, --display | Next start. --display macvisor|apple is for Windows VMs: Virtio-MVZ, MacVisor's own display device (the default: any resolution, follows the window) or Apple's (1024 x 768 only). A suspended VM resumes with the display it was saved with. See The display. |
--disk <GiB> | Grows the boot disk of a stopped VM. Never shrinks. A cloud-image Linux VM grows its filesystem at next boot. |
--iso <file|none> | Attaches or detaches a disc image |
--balloon-target <GiB> | At once, on a running VM. Kept between a floor (a quarter of the VM's memory, at least 1 GiB, or 4 GiB for macOS) and the VM's memory. |
--clipboard | At once. both (default), to-vm, from-vm or off. See Clipboard. |
--density automatic|retina|standard | At once. A Windows VM on Virtio-MVZ: how many of the screen's pixels the guest draws per window point. automatic (the default) gives the screen's own until the guest's display would pass 4K, and fewer beyond, so full screen on a 5K display draws 3840 x 2160 rather than 5120 x 2880. retina always gives the screen's own, standard one per point. The agent sets Windows' scaling to match (200%, 150%, 100%), once for each user and density. See The display. |
--cloud-init on|off | Next start. A Linux or FreeBSD VM made from a cloud image: off makes it an appliance (no seed disk, setup or SSH checks; guest tools stay as they are, and a FreeBSD VM's agent gets its token on a small disc of its own), on brings its setup back as it was. Refused unless the VM is shut down. On a suspended VM, add --discard-state. set <vm> shows Cloud-init: on, off, or off (made without it). |
--qemu-ga on|off | Next start from shut down. A Linux VM's port for a QEMU guest agent in the guest (org.qemu.guest_agent.0). On for new Linux appliances. A FreeBSD VM has its own serial port for it, so it's refused there. |
--secure-boot on|off | Next start from shut down. UEFI Secure Boot for a Linux or Windows VM: the firmware starts only signed boot loaders (shim-signed distributions; Windows, with MacVisor's signed boot files). On for new Windows VMs. A Windows VM made by an earlier MacVisor gets signed boot files when it's turned on, which needs it shut down. If the VM then stops right after it starts, see repair-boot. |
--tpm on|off | Next start from shut down. A Linux or Windows VM's TPM 2.0, sealed to this Mac's Secure Enclave. On for new Windows VMs. off keeps the TPM for when it's on again; tpm reset erases it. A Windows VM made by an earlier MacVisor gets MacVisor's TPM driver on its EFI partition when it's turned on, which needs it shut down. A Linux VM gets the driver from its guest agent, and has the TPM from the start after that. See TPM and boot files. |
--agents off|view|control | At once. What AI agents connected to mvz mcp serve may do with the VM. Granting control asks first at a terminal, unless --yes. See AI agents and MCP. |
--auto-snapshots off|hourly|daily|weekly | At once. Automatic snapshots while the VM runs, off by default. Keeps the last 24 hourly, 7 daily or 4 weekly ones unless --auto-snapshots-keep says otherwise. Not for templates. |
--auto-snapshots-keep <n> | How many automatic snapshots to keep, 1 to 30. A lower number takes effect at the next one. |
--backup on|off | At once. Scheduled backups, off by default. Set on its own, apart from the other options. |
--backup-to <folder> | At once, and turns backups on. A local, external or network folder outside the storage locations; each VM's backups go in a folder of their own in it. |
--backup-schedule daily|weekly|<day> | Daily (the default), weekly on Sunday, or weekly on the day you name (monday, tue…). |
--backup-at HH:MM | The time, in this Mac's time zone (03 unless set). |
--backup-keep <n> | How many backups to keep, the newest (7 unless set). |
--visual-effects reduced|full | At once, or at the next sign-in. A Windows VM's transparency and animations: off (reduced) or Windows' defaults (full). The agent applies it once for each Windows user, so a later change in Windows' own Settings stays. set <vm> shows unset for a VM made before the setting existed. See Visual effects. |
--sound on|off | At once, even while the VM runs. A Windows VM's sound in its own window: MacVisor's audio device, with speakers and a microphone, which every new Windows VM gets. A VM made without the device, by an earlier MacVisor, can't be given it. Remote Desktop has sound either way. For a VM with the device, set <vm> shows Sound: on, microphone on, on, microphone off or off. See Sound. |
--microphone on|off | At once. With a Windows VM's sound on: whether Windows can record from the Mac's microphone, which is opened only while Windows records (macOS asks the first time). on needs sound on; off is kept while sound is off. |
--network takes nat, bridged, bridged:<interface>, or a custom network's name or id.
A suspended VM resumes with the settings it was saved with. Most changes are kept and apply after it shuts down. --disk and a --cloud-init change are refused on a suspended VM. Add --discard-state to drop the saved session and make the change, or resume the VM and shut it down first.
mvz set dev --cpus 8 --memory 16
mvz set dev --disk 128
mvz set dev --network bridged:en0
mvz set dev --iso none
mvz set win11 --density retina --visual-effects full
mvz set win11 --sound on --microphone off
mvz set router --cloud-init off
mvz set dev --tpm on
mvz set dev --auto-snapshots hourly --auto-snapshots-keep 12
Disks and network adapters
mvz disks list <vm>
mvz disks add <vm> --size <GiB> [--format asif|raw] [--sector-size 512|4096|4k]
mvz disks remove <vm> <disk-id> [--yes]
mvz nics list <vm>
mvz nics add <vm> [--network <network>]
mvz nics set <vm> <nic-id|primary> [--network <network>] [--connected|--disconnected]
mvz nics remove <vm> <nic-id>
Adding or removing a disk needs the VM shut down, with no saved session. disks remove is refused for a VM protected from deletion. A new disk is blank, ASIF with 512-byte sectors unless --format raw or --sector-size 4096. disks list has FORMAT and SECTOR columns, and show prints each disk's format and sector size (disks[].sectorSize in --json). A VM has up to four network adapters. A new one uses NAT unless --network puts it on nat, bridged, bridged:<interface> or a custom network by name or id. nics set moves an adapter, as VM Settings → Network does (primary is the first adapter, as with set --network). Adapter changes take effect at the VM's next start from shut down, except --disconnected and --connected: they unplug or plug in the adapter's virtual cable, at once on a running VM, and the VM starts that way from then on. An unplugged adapter stays unplugged until you plug it in, through restarts and copies of the VM (an older MacVisor starts it plugged in). nics list shows each adapter's network and, in its CABLE column, whether it's plugged in; show has an Unplugged row.
Shared folders
mvz shares <vm>
mvz shares <vm> add <dir> [--read-only] [--guest-path <path>]
mvz shares <vm> update <dir> [--read-only|--read-write] [--guest-path <path>]
mvz shares <vm> remove <dir|tag>
Shares are read-write unless --read-only. A Linux guest with the agent mounts each folder at the Mac's path, or at --guest-path. A macOS guest mounts shares under /Volumes/My Shared Files, and a Windows guest on drive Z:. Changes apply at the next start from shut down.
Clone
mvz clone <vm> <name> [--linked | --snapshot <id>] [--location <location>]
- A full copy is near-instant on the same APFS volume.
--linkedshares the source's disk and stores only the clone's changes. The source must be stopped. See Linked clones.--snapshot <id>copies the VM as it was in that snapshot, also for a VM made from a cloud image or a template. A clone of a live snapshot boots from its disk, as after a power cut.
A copy turns off anything that would clash with the original: start at login, port forwards on host ports in use, and captured USB devices. Disk images the original writes to are attached read-only. clone lists what it changed.
Snapshots
mvz snapshots list <vm>
mvz snapshots take <vm> [--name <name>] [--notes <text>] [--disk-only]
mvz snapshots revert <vm> <snapshot-id> [--resume] [--safety-snapshot | --no-safety-snapshot] [--yes]
mvz snapshots delete <vm> <snapshot-id> [--yes]
mvz snapshots delete <vm> --agent-checkpoints [--yes]
mvz snapshots export <vm> <snapshot-id> <folder> [--name <name>] [--json]
list shows snapshots oldest first, the latest last; --json lists them newest first, as the service does, so a script's first entry is the latest. Its AUTO column ("automatic": true with --json) marks automatic snapshots. A running VM's snapshot includes its memory unless --disk-only. delete removes a snapshot for good. It asks first on a terminal, and elsewhere needs --yes. delete --agent-checkpoints removes every checkpoint AI agents took of the VM or sandbox, and nothing else: not your own snapshots, nor the safety snapshots taken before agents' rollbacks. revert --resume continues the saved session, or boots from the snapshot's disk if the session can't be restored. On a terminal, revert asks first and takes a safety snapshot of the current state, as the app's revert sheet does. It's named "Before revert" with the date and time. If it can't be taken, nothing is reverted. With --yes it neither asks nor takes one, as in a script. --no-safety-snapshot skips it, and --safety-snapshot takes one with --yes or in a script. On a drive that can't clone files, a snapshot or revert waits for the full copy, up to six hours. See Snapshots and recovery.
export saves a snapshot as a VM that import takes on any Mac, also while the VM runs. The bundle is <folder>/<vm> <snapshot>.mvz (.mvzt for a template's snapshot), or <name>.mvz with --name. A name that's taken gets -2, -3 and so on, as export does. --json prints the bundle's path at result.bundlePath and the snapshot's id at result.snapshotID. Until the bundle is written, the snapshot can't be deleted or reverted to, and the VM can't be moved, converted or deleted. A snapshot still being taken can't be exported. mvz tasks cancel <id> stops it and leaves nothing behind. See Backups.
Flatten and trim
mvz flatten <vm> [--yes] # make a layered disk one self-contained file
mvz trim <vm> [--json] # have a running Linux VM discard its unused disk space
flatten copies the blocks a linked clone reads from its base into its own disk. The VM must be stopped and have no snapshots. A flattened clone can be exported. It asks first on a terminal, and elsewhere needs --yes.
trim says what the guest discarded and how much space the Mac got back, which can be none. --json has trimmedBytes (what the guest discarded), macReleasedBytes (what the Mac got back) and keptByClonesBytes (what snapshots, clones or a linked clone's base layers still hold).
Backups
mvz backup <vm> [--to <folder>] # back it up now
mvz backups list <vm> [--in <folder>] # its backups, newest first, and the schedule
mvz backups list --in <folder> # every backup in a folder
mvz backups restore <backup> [--name <name>] [--location <location>]
mvz set <vm> --backup-to <folder> [--backup-schedule daily|weekly|<day>] [--backup-at HH:MM] [--backup-keep <n>]
backupwrites the whole VM to its backup folder, or to--to <folder>, as<folder>/<vm> (<short id>)/<vm> <date time>.mvz, and checks every file. A running VM keeps running: it's backed up from a disk snapshot, deleted afterwards. In the backup folder, backups past the newest the VM keeps are deleted; one made with--toelsewhere is yours to keep.mvz tasks cancelstops it and leaves nothing behind.backups list --in <folder>with no VM reads the backups' manifests, so it lists those of VMs no longer on this Mac too.backups restoreimports a backup, asimportdoes: a new identity, and no shared folders, USB devices, start at login, high availability or backups. It needs no license.showhas a Backups row: on or off, the last backup and the next.
See Backups.
Export, import and move
mvz export <vm> <folder>
mvz import <bundle> [--location <location>] [--move]
mvz import <vm.vbvm|vm.utm|tart-vm-folder|vm.pvm|vm.macvm|disk-image> [options]
mvz import --scan [<path>...]
mvz move <vm> <location>
exportcopies a VM's bundle to a folder, as<name>.mvz(<name>.mvztfor a template). Flatten a linked clone first.importadds a.mvzor.mvztbundle (or a.macvisorone from an earlier build) with a new identity (id, MAC addresses, machine identifier).--movemoves the bundle instead of copying it: once the VM is in place, the original goes to the Trash, or, where it can't (a drive with no Trash), stays where it is andimportnames it. It's refused while a VM with the bundle's id runs, and for a bundle your library already lists, however its path is spelled (another letter case, a symlink). Usemvz movefor that.importalso brings VMs from VirtualBuddy, UTM, tart and Parallels Desktop, and makes a VM from a raw, ASIF or qcow2 disk image, with a progress line in a terminal.--scanlists what it can import: with no paths, what VirtualBuddy's, tart's and Parallels Desktop's folders hold, read-only; UTM's folder only when you name it.--movesends the original to the Trash once the VM is made.showhas an Imported row saying where an imported VM came from (importedFromin--json). See Import VMs from other apps.
An imported VM loses settings that point outside it, and import lists the changes:
- Shared folders and external disk images are removed.
- Captured USB devices are cleared, and start at login, high availability and backups are turned off.
- Port forwards and remote access listen on
127.0.0.1only. - A suspended session is discarded. Live snapshots keep their disks but lose their memory.
A bundle that references disk files outside itself, or contains links, is refused. So is a linked clone whose base disk has changed since it was made.
TPM and boot files
mvz tpm reset <vm> [--yes] # erase the VM's TPM; a new one at its next start
mvz tpm export <vm> <file> [--passphrase-file <path>] # seal the TPM with a passphrase, for another Mac
mvz tpm import <vm> <file> [--passphrase-file <path>] [--yes] # take an exported TPM in, sealed to this Mac
mvz repair-boot <vm> [--reset-keys] # put MacVisor's boot files back on a Windows VM
MacVisor keeps a Linux or Windows VM's TPM sealed to this Mac's Secure Enclave, so a copy of the VM on another Mac can't open it, as a PC's TPM stays with the PC. Each of these commands needs the VM shut down, and tpm reset, tpm import and repair-boot need it with no suspended session.
tpm reseterases the TPM. The VM gets a new one at its next start, which Windows sets up again: BitLocker with a TPM protector asks for its recovery key, and Windows Hello for a new PIN. In a Linux VM, what was sealed to the old TPM, such as LUKS keys enrolled withsystemd-cryptenroll, needs setting up again.tpm exportseals the TPM with a passphrase of at least 8 characters instead. Move the VM (an export, a backup or a copy of its bundle) and the file, then runtpm importon the other Mac. The TPM is then sealed to that Mac, and the file isn't needed again: keep it as safe as the VM's BitLocker recovery key, or delete it. Windows sees the same TPM, so BitLocker and Windows Hello carry on. A snapshot taken on the first Mac holds the TPM sealed to that Mac: reverting to one on the other Mac needs the import again.- The passphrase is typed (twice for an export), piped on standard input, or read from
--passphrase-file, never given on the command line. - On a terminal,
resetandimportask first; without one they need--yes. repair-bootis for a Windows VM that stops right after it starts. MacVisor writes its boot files to the VM's EFI system partition from the Mac and, with Secure Boot on, sets up the VM's Secure Boot keys again. Nothing on Windows' own partition changes.--reset-keysclears the Secure Boot keys first, with any change Windows made to them; Windows Update adds its updates again.
See TPM and Secure Boot, BitLocker and A TPM for Linux VMs.
Networks
mvz networks
mvz networks create <name> [--host-only] [--subnet <a.b.c.0/24>]
mvz networks rm <network>
mvz networks reserve <network> <vm> <ip>
mvz networks auto-addresses <network> on|off
mvz networks move-reservations <network>
mvz networks apply <network>
mvz networks firewall <network> [on|off] [--inbound allow|deny] [--outbound allow|deny]
[--allow <rule>]... [--deny <rule>]... [--clear]
createmakes a shared network (NAT to the outside) or, with--host-only, an isolated one.--subnetworks only for host-only networks. On macOS 27.2 a fixed subnet leaves VMs with no network link, so leave it automatic. See Addressing.networkslists each network's subnet. A*marks a subnet that changed because another app took the old one.- Reservations. Each VM adapter on a network gets its own reserved address once the subnet is known.
auto-addresses offreleases them.reservepins an address by hand.move-reservationsmoves reservations from an old subnet into the current one. A reservation saved while VMs use the network is markedwaitinguntil the network is created again;applydoes that now, unplugging its running VMs for a few seconds. See Automatic reservations and DHCP reservations. rmis refused while a VM uses the network.
Firewall rules
A rule is in|out[:tcp|udp|icmp|any[:<ports>[:<cidr>]]]. Each part is optional from the right.
- Rules keep the order you type them,
--allowand--denymixed. The first match wins, then the default for that direction. - New rules go after existing ones.
--cleardrops the existing ones first.offdisables the firewall and keeps the rules. - The change is checked and applied before the command returns, whether or not the app is open. A rule that can't be enforced is refused.
mvz networksshows each firewall ason,pending(saved, not in force) oroff.
mvz networks create lab
mvz networks create isolated --host-only
mvz networks reserve lab dev 192.168.64.20
mvz networks firewall lab on --outbound deny --allow out:tcp:443 --allow out:udp:53
mvz networks firewall lab --clear --deny out:any::10.0.0.0/8 --allow out:tcp:443
In the last example the deny comes first, so TCP 443 to 10.0.0.0/8 is blocked and TCP 443 anywhere else is allowed. See Network firewall.
AI sandboxes (Tech Preview)
mvz sandbox create [<dir>] [--agent goose|claude|codex|aider|opencode|none] [--os linux|macos] [--egress allowlist|open|none]
[--cloud|--no-cloud] [--provider-tools|--no-provider-tools] [--whole-repo]
mvz sandbox set <name> [--cloud on|off|default] [--provider-tools on|off|default]
mvz sandbox list
mvz sandbox show <name>
mvz sandbox run <name> -- <prompt...> # run the agent headless
mvz sandbox attach <name> # interactive agent session
mvz sandbox shell <name>
mvz sandbox exec <name> -- <command...>
mvz sandbox diff <name> [--patch]
mvz sandbox apply <name> [--branch]
mvz sandbox checkpoint <name> [<label>]
mvz sandbox rollback <name> [<label>]
mvz sandbox allow <name> <host|preset>...
mvz sandbox deny <name> <host|preset>...
mvz sandbox log <name> [--egress]
mvz sandbox discard <name> [--yes]
mvz ai status # gateway, local servers, keys
mvz ai local <auto|ollama|lmstudio|omlx|llamacpp|url>
mvz ai keys set <anthropic|openai|openrouter|local>
mvz ai default-model <name>
mvz models [pull <model>] # coding models sized to this Mac, with their licenses
mvz models server [status | start | stop | install <ollama|llama.cpp|omlx>]
mvz mcp serve [--allow-create] [--project <dir>]... [--allow-open-egress] # MCP over stdio for agents on the Mac
mvz mcp clients # the agent apps on this Mac, and whether each has MacVisor
mvz mcp install <client>... [--allow-create] [--dry-run] # add MacVisor's MCP server to them
mvz mcp uninstall <client>... [--dry-run] # take it out again
mvz agents # the VMs agents may use, and how far
mvz agents log [--lines <n>] [-f] # what agents did through MCP; -f follows it live
mvz set <vm> --agents off|view|control # open a VM to agents, or close it
A sandbox is a VM for one project and one coding agent. The agent reaches models through MacVisor's AI gateway, so no API key goes into the VM. A sandbox set up with a local model uses no cloud models unless --cloud or set --cloud on allows them, and with allowlist or none egress the provider's own internet tools are refused unless --provider-tools or set --provider-tools on allows them. Its changes come back only through diff and apply. ai keys set reads the key from a prompt or standard input, never from the command line. mvz help sandbox, mvz help ai and mvz help mcp list every option. See AI sandboxes.
mcp serve also reaches the VMs you open to agents with set --agents, and nothing else. With --allow-create it can make VMs from a catalogue image, a template you opened or a snapshot of a VM agents control, and delete those again, and make sandboxes from projects in its own folder, in a --project folder or in a folder allowed in Settings → AI → Agent Access. --allow-open-egress lets an agent give a sandbox open internet access. Its copy_to_vm and copy_from_vm tools copy files between those folders and a VM by path, any size. Agents' checkpoints are kept to the newest 10 per VM or sandbox. See AI agents and MCP.
mcp install sets the server up in the agent apps you name, claude-code, claude-desktop, codex, cursor, vscode, zed, goose and gemini, as Settings → AI → Agent Access → Connect Agents does. It changes only their macvisor entry, keeps a copy of the file as it was beside it (<file>.macvisor-backup), and gives them the absolute path /usr/local/bin/mvz, or the CLI in /Applications/MacVisor.app. --dry-run shows the change and makes none. See Connect an agent.
Housekeeping
mvz tasks [<vm>] # long-running work
mvz tasks status <task-id>
mvz tasks cancel <task-id>
mvz service status
mvz service logs [--tail <lines>]
mvz service restart [--yes | --cancel]
mvz service stop [--yes | --cancel]
mvz service at-logout [shutdown|suspend]
mvz version
-
Tasks are the long operations in the app's task pane: downloads, macOS installs, exports, transfers, flattening. A cancelled download keeps what it fetched and stays stopped. A cancelled macOS install leaves the VM stopped, ready for Erase and Install macOS….
-
service statusshows the service's PID, build and state, and any storage location still waiting for permission. If the service runs an older build thanmvzafter an update, it says so. Runmvz service restart. -
service restartandservice stoplet work in progress finish first. Running VMs keep running. If there's work to wait for, such as a clone, an export or a VM being created, they name it and ask first. With nobody to ask, as in a script, pass--yes. While it waits, the service takes no new changes, but high availability and VM Watchdog still restart VMs.--cancelcalls off a restart or stop that is still waiting: the service carries on as before, and the work in progress isn't affected. A shutdown of the Mac, or a logout, can't be called off.
-
service at-logoutsets what running VMs do when the Mac logs out, restarts or shuts down. It is the same setting as Settings → General → Logout and Shutdown.shutdown(default) asks each guest to shut down and powers it off after 30 seconds. VMs set to start at login boot fresh.suspendsaves each VM's memory to disk, and VMs set to start at login resume. After a macOS update a saved session can't be restored, so those VMs boot from disk instead.
See Run and control VMs.
MacVisor adds its background service as a login item when it opens, and its Setup Assistant repairs it (Open Setup Assistant in the dashboard's Background Service panel). Remove Background Service is in the same panel. macOS lets only the app do these, so the CLI can't. mvz service agent-status says whether launchd has the service.
Scripting examples
Every command exits non-zero on failure, so && chains and set -e work. The commands that ask first on a terminal take --yes, so a script you run from Terminal doesn't stop at a question. See Confirmation.
A throwaway test VM
mvz create ubuntu ci-42
mvz shares ci-42 add "$PWD"
mvz start ci-42 --headless && mvz wait ci-42 --ssh
mvz exec ci-42 --wait --workdir "$PWD" -- make test
mvz poweroff ci-42 && mvz rm ci-42 --yes
The working tree is shared read-write at the same path, so build paths match on both sides. The VM is a linked clone of the Ubuntu template, so it needs no download after the first run and uses only the space the job writes.
mvz rm moves the VM to the Trash. On a CI host, empty the Trash regularly to get the space back.
Snapshot, try something, roll back
mvz snapshots take dev --name baseline --disk-only
mvz exec dev -- sudo ./risky-upgrade.sh
mvz snapshots revert dev baseline --yes # powers off and restores the disk
mvz start dev --headless
To keep the VM running throughout, take a live snapshot (omit --disk-only) and use revert --resume.
A headless macOS 27 VM with SSH
mvz create macos-27 mac-ci --user builder --password-file ./builder-password --ssh --cpus 6 --memory 12
mvz wait mac-ci --ssh --start # the install, then the first boot
mvz exec mac-ci -- sw_vers
mvz autostart mac-ci on
The first boot creates the builder account and turns on Remote Login. MacVisor then installs its agent and authorises your SSH keys, so exec needs no password. See Always-on host.
App-only features
These are done in the app: converting a raw disk to ASIF, shrinking a disk, network-level port forwards, VLAN interfaces, the per-network DHCP, DNS, NAT and RA switches, creating an empty VM with no ISO, installing Ubuntu Desktop in a new VM, typing the Mac's clipboard into a VM (Type Clipboard), and adding storage locations. mvz set <vm> --iso attaches installation media to an existing VM.
DeltaSync