Commands · reference

vmlab template

14 min read · 2026-09-02 · vmlab 0.9

vmlab template manages the template store and registry distribution: building templates a file declares, listing and pruning what the store holds, moving templates through archives, and pushing and pulling them over OCI registries. Every subcommand is a request to the supervisor (vmlabd), which owns the store and serialises writes to it. The CLI starts the supervisor when it is not running, parses the flags, asks, and renders the answer. A build or push started here is the same operation the supervisor runs for anyone else, so it can be stopped from another terminal.

sh
vmlab template <COMMAND>
SubcommandMeaning
buildBuild templates defined in a lab or template file.
stopStop a build or push running for a template, whoever started it.
listList templates in the store.
searchSearch a registry for published templates.
rmRemove one template version.
cleanPrune superseded builds, keeping the latest per template.
exportExport a template to a portable archive.
importImport a template from an archive.
pushPush a template to an OCI registry.
pullPull a template from an OCI registry.
loginLog in to an OCI registry.
registryManage the OCI namespaces search looks in.
-h, --helpPrint help.

Store references are spelled <arch>/<name>[@<version>][#<variant>]. Without a version the reference resolves to the newest version in the store. Registry references are spelled ghcr.io/owner/name[:version].

Exit status is 0 on success. Failures from the supervisor carry the protocol error codes in Wire protocol and error codes: conflict (5) when an operation is already running for the template, not_found (4) when a template or a running operation does not exist, invalid_argument (2) when a name is ambiguous, and failed (1) for a store or registry operation that was attempted and did not succeed. A failure before any request is sent, such as a missing file or a supervisor that does not start, exits 1. Each subcommand below names the codes it can produce.

vmlab template build

sh
vmlab template build [OPTIONS] [NAME]
OptionMeaning
[NAME]Build only the named template. Default: every template in the file. <name>@<version> pins the version, the same as --version.
-f, --file <FILE>File containing template {} blocks. Default: the vmlab.wcl of the lab the shell is in.
--version <VERSION>Pin an explicit version instead of auto-incrementing. Requires a single target template. With --variant, the installed version to build the variants onto.
--variant <VARIANT>[,<VARIANT>…]Build or rebuild only these variants onto the newest installed version, or the pinned one, leaving the base and every other variant as they are. Requires NAME.
-h, --helpPrint help.

build asks the supervisor which templates the file declares, filters them by NAME when one is given, and starts one build per target, in file order. A relative --file is made absolute against the shell's directory before it is sent, because the supervisor does not stand where you do. The build is filed under the lab the file belongs to, so a console watching that lab sees it. A bare template file with no lab block is filed under the store itself.

The command subscribes to the supervisor's event stream before asking, then prints every template.op.log line as it arrives until the operation reports done, cancelled, or an error. One Ctrl-C sends a stop request and keeps following until the supervisor has cleaned up. A second Ctrl-C exits immediately and warns that the build may still be running.

A template that declares variant {} blocks builds its base first and then each variant on it, the default first and the rest in file order; the log names each as it starts (`building variant x86_64/windows-11@26100.1742.6#win3d (2/2)`). A variant that fails stops the build with the base and every variant before it installed, and the error names the variant and the --variant command that retries it. See Lab file: template and source for what a variant build does. The base is recorded with variants_expected = true, so until its default variant is installed a lab refuses every reference to the version rather than cloning the unsealed base.

--variant skips the base and builds only the named variants onto a version already in the store. It is refused when the base is not installed, when the file does not declare a named variant, when the result would leave several variants installed but not the default, and while a machine's clone is backed by a variant it would replace. A rebuilt variant replaces the old one's disk and metadata together.

It refuses locally, with exit status 1, when the file declares no template {} block, when NAME matches nothing in the file, and when --version is given with more than one target. The supervisor answers conflict when a build or push is already running for that template, not_found when the named template is not in the file it loaded, and invalid_argument when the name is declared for more than one architecture and none was chosen. A build step that fails ends the stream with an error and exit status 1.

sh
cd examples/templates
vmlab template build win2022
vmlab template build -f templates.wcl --version 1.4.0 alpine
vmlab template build windows-11 --variant win3d
vmlab template build windows-11@26100.1742.6 --variant standard,win3d

Exit status is 0 on success, conflict (5), not_found (4), invalid_argument (2), or 1.

vmlab template stop

sh
vmlab template stop [OPTIONS] <NAME>
OptionMeaning
<NAME>Template name, as the file declares it.
--arch <ARCH>Architecture, when the name is declared for more than one. Without it, the arch the lab file here declares the template for.
-h, --helpPrint help.

stop cancels the operation running for the template in the lab the shell is in, whether it was started from this terminal, another one, or the console. A build and a push are stopped by different requests, so the command first asks to stop a build. If the supervisor answers conflict, the running operation is a push, and the command sends the push stop instead. On success it prints stopping <arch>/<name>; the terminal that started the operation sees it end as cancelled.

sh
vmlab template stop win2022

Exit status is 0 on success. not_found (4) means nothing is running for that template in this lab. Any other failure is failed (1).

vmlab template list

sh
vmlab template list [OPTIONS]
OptionMeaning
--jsonEmit a JSON array of template metadata instead of a table.
--remoteAlso ask each template's registry whether this exact version is published. Adds a REMOTE column. Requires network access.
-h, --helpPrint help.

The table has columns ARCH, TEMPLATE, VERSION, SIZE, and CREATED. The TEMPLATE column shows the template's full registry path when its metadata names one, and the bare store name otherwise. An empty store prints no templates in the store. With --remote the extra column reads yes when the version is published, no when the registry is reachable and does not have it, local when the template names no registry, and ? when the registry reference could not be asked. Under --json the remote answer is folded into each entry as a remote field.

A version with variants lists each one on its own line beneath it, as #<name> in the TEMPLATE column. The VERSION column reads default for the default variant and STALE for one built on an earlier base, which is refused until it is rebuilt. SIZE is the variant overlay's own size. Under --json each entry carries a variants array of objects with name, default, stale, description, created, sha256 and size; it is empty for a base-only template.

A version built from a definition with variants that has no default variant installed reads variants not built after its CREATED date, or default variant not built when other variants remain. A lab refuses it until vmlab template build <name>@<version> --variant <variant> builds them. Under --json such an entry carries `variants_expected: true with no default in its variants` array.

sh
$ vmlab template list
ARCH     TEMPLATE                          VERSION          SIZE     CREATED
x86_64   ghcr.io/vmlabdev/win2022          1.2.0            9.8G     2026-08-30
           #standard                       default          410.2M   2026-08-30
           #win3d                                           1.1G     2026-08-30
x86_64   alpine                            0.3.1            180.0M   2026-08-12

Exit status is 0 on success and failed (1) when the store cannot be read.

sh
vmlab template search [OPTIONS] [QUERY]
OptionMeaning
[QUERY]Case-insensitive substring to match the template name. Default: everything.
--registry <REGISTRY>Registry namespace to search. Default: every configured namespace, which starts as the vmlab registry.
--arch <ARCH>Only show templates that have this architecture.
--kind <KIND>Search VM templates or container images: vm or container. Default: vm.
--jsonEmit a JSON array instead of a table.
-h, --helpPrint help.

search reads the catalog of one namespace, or of every namespace template registry list shows, and prints TEMPLATE, ARCH, and VERSION per match. For a template, VERSION is the version the repository's latest tag names and ARCH every arch published at it; a repository with no latest shows its highest version tag. A namespace that fails while others answer is reported as a warning on stderr beside the results. When every namespace fails the search itself fails. No match prints how many namespaces were searched.

sh
vmlab template search win --arch x86_64
vmlab template search --kind container nginx --json

Exit status is 0 on success, including no match, and failed (1) when no namespace answered.

vmlab template rm

sh
vmlab template rm [OPTIONS] <REFERENCE>
OptionMeaning
<REFERENCE>The version to remove, <arch>/<name>@<version>, or one variant of it, <arch>/<name>@<version>#<variant>.
--forceRemove even if it backs existing clones.
-h, --helpPrint help.

rm removes one exact store version, with all of its variants, and prints removed <arch>/<name>@<version>. A version whose base or any variant backs a machine's clone is refused unless --force is given, because the clone reads its image from the store; the refusal names the machines. Removing such a version with --force breaks every machine cloned from it.

With #<variant>, rm removes that one variant's overlay and its block in the version's metadata, and prints `removed <arch>/<name>@<version>#<variant>`. It is refused while a clone is backed by that variant, unless --force is given. It is always refused for the default variant while other variants remain, and for the last variant: either would leave a reference without # selecting the unsealed base. Remove the version instead.

sh
vmlab template rm x86_64/win2022@1.1.0

Exit status is 0 on success and failed (1) when the reference does not resolve, the version or variant backs a clone, or the variant cannot be removed on its own.

vmlab template clean

sh
vmlab template clean [OPTIONS] [FILTER]
OptionMeaning
[FILTER]Limit to a family: <arch>/<name>, <arch>/ for every name in an architecture, or <name> for that name in any architecture. Default: every template.
--keep <KEEP>Most-recent builds to keep per template, by version order. Default: 1.
-y, --yesActually delete. Without it the command only prints what it would remove.
--forceAlso remove builds that still back existing clones.
-h, --helpPrint help.

clean groups the store by <arch>/<name>, keeps the newest --keep versions of each, and lists the rest. Without --yes it is a dry run: each line reads would remove ... and the summary says to re-run with --yes. With --yes each line reads removing ... and the summary reports the count and the space freed. A build that backs a clone is listed as skipping ... backs a clone (use --force) and left alone unless --force is given. A store with nothing past the keep count prints nothing to clean.

--keep 0 is refused locally: use rm to remove a specific version.

sh
vmlab template clean                 # dry run, every family, keep 1
vmlab template clean x86_64/ --keep 2 --yes

Exit status is 0 on success and failed (1) when the store cannot be read or a removal fails.

vmlab template export

sh
vmlab template export <REFERENCE> <OUT>
OptionMeaning
<REFERENCE>The version to export, <arch>/<name>[@<version>].
<OUT>Output archive path, a .tar.zst file.
-h, --helpPrint help.

export writes one store version, disk and metadata, to a portable archive the supervisor creates at OUT. A version with variants takes them along: each variants/<variant>/disk.qcow2 and the full template.wcl with every variant block. A relative OUT is resolved against the shell's directory. It prints exported to <OUT> when done.

sh
vmlab template export x86_64/alpine@0.3.1 alpine-0.3.1.tar.zst

Exit status is 0 on success and failed (1) when the reference does not resolve or the archive cannot be written.

vmlab template import

sh
vmlab template import [OPTIONS] <ARCHIVE>
OptionMeaning
<ARCHIVE>An archive written by template export.
--overwriteReplace the version if the store already has it with a different disk.
-h, --helpPrint help.

import reads a template back out of an archive into the store and prints imported <arch>/<name>@<version>, followed by `with variants <names>` when it has any. The version comes from the archive's metadata. Every variant is checked before anything is installed: its overlay must hash to its block's sha256, its base_sha256 must be the archive's base, and its backing file must be ../../disk.qcow2. A variant that fails any of them refuses the whole archive. A version the store already holds is refused unless --overwrite is given.

sh
vmlab template import alpine-0.3.1.tar.zst

Exit status is 0 on success and failed (1) when the archive cannot be read, a variant fails its checks, or the version already exists.

vmlab template push

sh
vmlab template push [OPTIONS] <REFERENCE> [TARGET]
OptionMeaning
<REFERENCE>Local template <arch>/<name>[@<version>].
[TARGET]Registry repository, for example ghcr.io/owner/name. Default: the template's own registry field.
--source <SOURCE>Source repository URL to link the package to. Default: the git origin remote of the current directory, when it normalises to a web URL.
--prereleasePublish as a pre-release: move the latest-prerelease tag instead of latest.
-h, --helpPrint help.

push uploads one store version to a registry as an OCI artefact, chunked, and moves a floating tag onto it: latest by default and latest-prerelease with --prerelease. The version tag is always written. The command follows the push's log the way build does and prints pushed <arch>/<name>@<version> to <target> (moved <tag>) at the end, with the source link when one was sent. Ctrl-C stops the push.

A version with variants pushes each variant as a manifest of its own, tagged <version>--<variant>: the base's chunks, then the overlay's. The base's chunks are already in the registry by then, so a variant uploads only its overlay. The log says, for the base and for each variant, how many chunks it uploaded and how many the registry already held. A stale variant refuses the push before anything is uploaded; rebuild or remove it first.

The base's config blob carries variants_expected when the version was built with variants, and a pull keeps it, so a pulled base whose variants are not built is refused by a lab exactly as the pushed one is. A vmlab release older than the marker pulls such a base as an ordinary base-only template.

The registry needs credentials from template login first. A template with no registry field and no TARGET is refused with no push target. A push already running for the template is refused with conflict.

sh
vmlab template push x86_64/win2022 ghcr.io/vmlabdev/win2022

Exit status is 0 on success, conflict (5) when a push or build is already running, and failed (1) for a missing target, a refused upload, or a cancelled push.

vmlab template pull

sh
vmlab template pull [OPTIONS] <TARGET>
OptionMeaning
<TARGET>Registry reference, for example ghcr.io/owner/name:version. A #<variant> suffix also pulls that variant.
--arch <ARCH>Architecture to pull. Required when the reference is a multi-architecture index.
--overwriteOverwrite the version if the store already has it.
--all-variantsPull every variant of the version, not only the default and the one named.
-h, --helpPrint help.

pull downloads a published template into the store and prints pulled <arch>/<name>@<version> into the store. A reference without a tag pulls latest. A multi-architecture index with no --arch is refused, because the store keys templates by architecture. A version the store already holds is used as it stands when its disk is the published image, and refused without --overwrite when it differs. vmlab up and vmlab pull pull a machine's template on demand, so this verb is for fetching ahead of time or for a template no lab file names yet.

A template with variants always brings its default variant, so a reference without # picks the same variant in the store as in the registry. #<variant> adds that variant, --all-variants adds every one, and an unknown name is refused with the list of variants the template has. A base or a variant the store already holds with the same digest is not downloaded again, and what arrives is added to the version, keeping the variants already installed. The answer lists them: `pulled <arch>/<name>@<version> with variants standard (default), win3d into the store`.

sh
vmlab template pull ghcr.io/vmlabdev/alpine:0.3.1 --arch x86_64
sh
vmlab template pull 'ghcr.io/vmlabdev/windows-11:latest#win3d' --arch x86_64

Exit status is 0 on success and failed (1) when the registry refuses, the architecture is missing, the variant is unknown, a chunk or a reassembled disk fails its digest, or the version already exists with a different disk and no --overwrite.

vmlab template login

sh
vmlab template login --username <USERNAME> --password <PASSWORD> <REGISTRY>
OptionMeaning
<REGISTRY>The registry host, for example ghcr.io.
-u, --username <USERNAME>The account name.
-p, --password <PASSWORD>The password or token.
-h, --helpPrint help.

login stores credentials for a registry host with the supervisor, which uses them for every later push and pull against that host. It prints logged in to <REGISTRY>. The password is taken from the command line, so on a shared machine pass a token with limited scope rather than an account password.

sh
vmlab template login ghcr.io -u wil -p "$GHCR_TOKEN"

Exit status is 0 on success and failed (1) when the credentials cannot be stored.

vmlab template registry

sh
vmlab template registry <COMMAND>
SubcommandMeaning
list [--json]List the configured namespaces and what each is used for.
add <NAMESPACE> [--use-for <USE_FOR>]Add or update a searchable namespace. --use-for is vms, containers, or both. Default: both.
remove <NAMESPACE>Remove a searchable namespace.
-h, --helpPrint help.

The namespaces are what template search looks in when no --registry is given, and each is marked for VM templates, container images, or both. list prints NAMESPACE and USE columns. add prints added <namespace> and replaces the entry when the namespace is already configured. remove prints removed <namespace>.

sh
vmlab template registry add ghcr.io/vmlabdev --use-for vms
vmlab template registry list

Exit status is 0 on success and failed (1) when the namespace file cannot be read or written.