Commands · reference

vmlab lab

6 min read · 2026-10-06 · vmlab 0.9

vmlab lab manages running labs host-wide, by name rather than by the current directory. The supervisor keeps a registry of every lab daemon it started, and these verbs read it and act on the daemons behind it. See How vmlab runs a lab for the supervisor and the per-lab daemons.

sh
vmlab lab <COMMAND>
SubcommandMeaning
listList every tracked lab: name, state, and directory.
infoShow detailed status (machines and segments) of a running lab.
stopGracefully stop a running lab and release its name; clones retained.
restartRestart a lab's daemon so it re-reads vmlab.wcl.
destroyStop a lab and delete its clones and local state.
moveMove a stopped lab's working data to the lab in this directory.
-h, --helpPrint help.

The read-only subcommands never start the supervisor: with none running the registry is empty and list says so. None of them starts a lab daemon either; a lab that is not running is reported, not brought up.

vmlab lab list

sh
vmlab lab list [OPTIONS]
OptionMeaning
--jsonEmit a JSON array instead of a table.
-h, --helpPrint help.

Prints one row per registered lab with NAME, STATE and DIRECTORY. The state is running, stopped, stopping or failed. stopped means the daemon is up with no machine running, as after vmlab vm stop of every machine or an up that failed; a full vmlab down or vmlab lab stop removes the lab from the list instead. failed means the daemon exited without being asked to. With no supervisor or an empty registry it prints no running labs. Under --json each entry carries name, root, pid and state.

vmlab lab info

sh
vmlab lab info [OPTIONS] <LAB>
OptionMeaning
<LAB>The lab's name.
-v, --verboseAdd the raw power state, readiness, and each machine's kind-specific detail.
-h, --helpPrint help.

The host-wide form of vmlab status. With the daemon reachable it prints directory: <root> and then the same report status prints, with the same --verbose detail. A lab that is registered but whose daemon does not answer, for example one in the failed state, prints one line from the registry, `lab "<name>" [<state>] (not reachable) directory <root>`, and exits 0. A name the registry does not know fails with lab "<name>" is not running.

vmlab lab stop

sh
vmlab lab stop [OPTIONS] <LAB>
OptionMeaning
<LAB>The lab's name.
--forceHard kill instead of the graceful ladder.
-h, --helpPrint help.

A full vmlab down by name, from any directory. It stops every machine in the named lab through the graceful ladder down describes, or kills them under --force, keeps the clones, and has the supervisor reap the lab daemon, which releases the lab's name. Prints lab "<name>" is down (clones retained). This is the way to give a name back when the directory that registered it is gone or out of reach (see The lab name is already registered).

A lab whose daemon does not answer but which the supervisor still has registered, for example one in the failed state, is released: the supervisor reaps any QEMU, swtpm, virtiofsd or smbd process the daemon left behind, and the verb prints `lab "<name>" is not running (released; any orphaned processes were reaped)`. A name the registry does not know prints lab "<name>" is not running. Both exit 0.

vmlab lab restart

sh
vmlab lab restart [OPTIONS] <LAB>
OptionMeaning
<LAB>The lab's name.
--jsonEmit the raw JSON reply instead of a confirmation.
-h, --helpPrint help.

Replaces the lab's daemon so it re-reads vmlab.wcl. This is not down followed by up: that stops every machine and re-runs provisioning, whereas this replaces only the daemon. It picks up an edit to the lab file without starting anything, and it recovers a daemon whose lab file no longer loads. You do not need it before vmlab up: a full down already reaps the daemon, and up, pull and vm start/restart replace a daemon that outlived a partial stop themselves when the file has changed. Run it from the lab's directory when the lab is not registered (after a full down or lab stop); by name elsewhere it needs the registration.

The lab must already be stopped. A fresh daemon cannot re-adopt machines the old one was running, and the old daemon's own shutdown stops them, so a lab with machines still running is refused with `lab "<name>" still has machines running — stop them first; a restarted daemon cannot re-adopt them`. A daemon that cannot answer a status request at all is not a veto, because a lab whose file no longer loads is exactly what this verb exists to recover.

Run inside the lab's own directory, the current directory is the root the supervisor is asked to restart from; run elsewhere, the registry's entry is used. The supervisor refuses a name already registered from a different directory. On success the verb pings the new daemon and prints lab "<name>" daemon restarted at <socket>; under --json it prints the supervisor's reply, which carries the new socket path.

vmlab lab destroy

sh
vmlab lab destroy <LAB>
OptionMeaning
<LAB>The lab's name.
-h, --helpPrint help.

The host-wide form of vmlab destroy: asks the daemon to stop every machine and delete the clones, volumes and lab-local state, and releases the lab at the supervisor so its daemon is reaped. With no reachable daemon but a registry entry, it removes the lab's .vmlab/ directory itself. A name the registry does not know fails with lab "<name>" is not running. Prints `lab "<name>" destroyed`.

vmlab lab move

sh
vmlab lab move [OPTIONS] <LAB>
OptionMeaning
<LAB>The lab's name. The vmlab.wcl in this directory must declare it.
--from <DIR>The lab root to move from. Defaults to the directory the lab is registered from; required once the name is released, for example after a full vmlab down there.
-h, --helpPrint help.

Hands a stopped lab, with its provisioned machines, to another directory that declares the same lab name, typically another git worktree of the same repository. A lab name is unique per host, so the worktrees share one lab, and this moves it to the one you work in now without building its machines again. Run it in the directory that should own the lab.

It refuses, and changes nothing, when this directory's lab file declares another name, when a machine of the lab is running (vmlab down there first), or when this directory's working data already holds machine data: a clone, a container's disk or a named volume (vmlab destroy here first). Working data without any, such as SMB state left by an earlier up, is replaced. When the two lab files declare a machine differently, or one declares a machine the other does not, it warns naming each one and continues: the next up runs this directory's file, and refuses a clone whose template changed (see vmlab up).

It then releases the name as vmlab lab stop does and refuses if any process still holds the old working data. It moves .vmlab/, or the VMLAB_WORK_DIR directory keyed by the old root, to the one keyed by this root. On one filesystem that is a rename. Across filesystems it copies the tree beside the destination, checks every file's content against the source, renames the copy into place and only then deletes the source, so a failed copy leaves the source whole. Clones, snapshots, firmware variables, TPM state and named volumes move with it. It removes smb/smb.conf, which the next up writes for the new root, and rebases each @dev machine's sync ledger onto the new root, so the workspace syncer reads what this checkout holds differently as host-side changes rather than conflicts.

Folders the lab points machines at do not move: shares, container bind mounts and @dev workspaces belong to the checkout. The verb lists each one that does not exist under this directory, such as a media folder git ignores. Last, it registers the lab from this directory and prints lab "<name>" now lives in <dir> — run vmlab validate`, then vmlab up`.

There is no --copy. Both directories declare one lab name, and a name is registered from one directory at a time, so a copy could not run beside the original without renaming one of the labs.

Examples

See what is running on this host:

sh
vmlab lab list
sh
NAME      STATE      DIRECTORY
ad-lab    running    /home/wil/labs/ad-lab
mixed-lab failed     /home/wil/labs/mixed-lab

Give back the name of a lab registered from a directory you cannot reach, keeping its clones:

sh
vmlab lab stop ad-lab

Free the host of a lab you started from another directory:

sh
vmlab lab destroy peer-b

Take over the lab another worktree built, from the worktree you work in now:

sh
cd ~/src/app-feature
vmlab lab move app-lab --from ~/src/app-main
vmlab up

Exit status

Exit status is 0 on success, including list with nothing running, stop on a lab that is not running, and info on a registered lab whose daemon does not answer. An unknown lab name, a lab with machines still running at restart, a daemon that fails the request, or a supervisor that does not come up exits 1 (failed). restart exits 5 (conflict) when the supervisor already tracks this name from another directory. move exits 5 when a machine or process of the lab is running, when this directory already holds machine data, or when the lab is registered from a directory other than --from; it exits 2 when a lab file declares another name, and 1 when the lab is not registered and no --from is given. A usage error exits 2.