The guide · explanation

Distributing templates over registries

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

The online way to share a template is an OCI registry: GHCR, Docker Hub, Harbor, or any self-hosted registry that speaks the OCI distribution API. vmlab publishes a template as an OCI artifact, not as a container image, and this chapter explains what that means, how a multi-gigabyte disk is carried, how tags map onto versions and architectures, and how credentials are found. The verbs are documented in vmlab template.

An artifact, not an image

A pushed template is an OCI manifest whose artifactType is a vmlab-specific string, whose config blob is the template's metadata, and whose layers are the disk in pieces, each with a vmlab media type. The typing is the point. A docker pull of a vmlab reference fails fast as "not a container image" rather than half-working, and `vmlab template pull` refuses a manifest whose artifact type is not vmlab's rather than installing a container's layers as a disk. The media type strings are frozen: they are part of the on-the-wire contract and never change.

Chunking

The qcow2 is split into fixed-size chunks, 512 MiB by default, each compressed with zstd and pushed as one ordered layer blob. The manifest's annotations record the chunk count, the chunk size, the total size and the digest of the assembled, uncompressed image. A pull downloads the chunks, reassembles them in order and verifies the whole-image digest before installing to the store. Everything streams through bounded buffers, so a 64 GiB image never lands in memory.

The size is chosen for GHCR, the expected home for most templates, which enforces a per-layer size limit and a per-upload timeout. The timeout is the binding constraint on realistic upstream bandwidth, and 512 MiB clears it with a wide margin while keeping retries cheap. Change it with oci_chunk_size in the host configuration.

Addressing

A registry reference is registry/owner/name[:tag], and the registry host is always explicit. vmlab treats the first path segment as a host only when it looks like one: it contains a dot, a colon, or is exactly localhost. A bare owner/name is rejected with a message asking for a registry, so nothing ever reaches Docker Hub by accident.

The tag is the template version. A push of x86_64/[email protected] to ghcr.io/owner/ubuntu-24.04 lands at the tag 24.04.4.1, and a pull of that reference installs the template into the store as x86_64/[email protected], with the originating reference recorded in its metadata. The store name is the last path component of the repository.

Moving tags and prefixes

Every push also re-points a moving alias. By default that is latest; with --prerelease it is latest-prerelease instead, so a pre-release never displaces the stable pointer. When a lab references a registry template by a moving alias, or by a build-counter prefix such as 26100.1742, vmlab resolves it against the registry's published tags to a concrete version before pulling, and a concrete version already in the store is used offline without contacting the registry at all.

Multi-arch

A tag may resolve through an OCI image index keyed by platform architecture. This maps the store's arch dimension onto OCI's own multi-platform mechanism: a push of the aarch64 build of a template adds a platform entry to the index beside the x86_64 one. Consistent with the store, the arch is always explicit. vmlab template pull requires --arch when the tag resolves to an index with more than one platform, and never silently assumes the host's.

Push and pull

vmlab template push <arch>/<name>[@<version>] [target] publishes a store entry. The target defaults to the registry field the template block declared, which the metadata records, so a template built for a known home needs no argument. --source links the package to a source repository URL, and defaults to the origin remote of the directory you run it in when that resolves to a web URL. The push is performed by the supervisor, streams progress to your terminal, and can be stopped from another terminal with vmlab template stop.

vmlab template pull <registry/owner/name:version> installs into the store and refuses to replace a version already there unless you pass --overwrite. A lab file can skip the explicit pull: a vm whose template is a registry reference, with an arch beside it, is pulled by the supervisor before the lab daemon starts, with progress events you can watch, and never re-pulled once present.

Updates are explicit

A template present in the store is never re-pulled implicitly, even when the registry has a newer build under the same moving tag. Run vmlab pull in the lab, or vmlab template pull, to fetch a newer version. Existing clones keep backing onto the version they were created from.

Credentials

vmlab reuses the Docker credential configuration already on the machine, so a ghcr.io login you made for docker just works. It reads ~/.docker/config.json, or $DOCKER_CONFIG/config.json, and honours both the inline auths entries and the credHelpers and credsStore fields, invoking the named docker-credential-<helper> binary the way Docker does. Registries that answer with a Bearer challenge get the standard token flow. A missing credential is never fatal: anonymous pulls of public templates must work, so it simply means an anonymous request.

For a machine with no Docker tooling, `vmlab template login <registry> --username <user> --password <secret>` validates the credential against the registry and stores it in the same Docker config file, so a later push or pull finds it.

Finding templates

vmlab template search lists the templates published under a registry namespace, filtered by a name substring, an arch, and whether you want VM templates or container images. The namespaces it searches are host-level settings managed with vmlab template registry add | list | remove. They are search roots, not secrets: credentials stay in the Docker config and are looked up by registry host.

Published templates

vmlab's own templates are published under ghcr.io/vmlabdev/vmlab-templates, the namespace vmlab template search looks in by default for VM templates. The packages are public: anyone can pull them anonymously, with no vmlab template login.

Pull before you build. A pull takes minutes; building the same template from its installer can take 30 to 45 minutes for Windows. Build from examples/templates, or the separate vmlab-templates repository, only to change a template or for an arch or version that is not published.

A lab can name a published template directly. arch is required, and vmlab up or vmlab pull fetches it into the store the first time:

wcl
vm "winsrv" {
  template = "ghcr.io/vmlabdev/vmlab-templates/windows-server-2025"
  arch     = "x86_64"
}

Or pull it into the store first. The store entry is named after the template itself, so a lab that already says template = "x86_64/windows-server-2025" works unchanged afterwards. A pull of a version already in the store reuses the stored copy when its disk is the same image, and refuses when it differs.

sh
vmlab template pull ghcr.io/vmlabdev/vmlab-templates/windows-server-2025 --arch x86_64

vmlab template search is the authority on which templates exist. It searches with whatever credentials you have stored, so it can list entries an anonymous user cannot pull. Its ARCH column shows the architectures of the highest version only, so a template whose architectures were published under different versions looks narrower than it is. This table lists every architecture anyone can pull, checked anonymously on 2026-10-02:

TemplateArchesNewest version
almalinux-10x86_6410.2.1
alpine-3.23aarch64, x86_643.23.8
archx86_6420260901.0
debian-13aarch64, riscv64, x86_64aarch64 13.20260617, riscv64 13.20260617.1, x86_64 13.20260617
fedora-42riscv6442.20250911.1
fedora-44aarch64, x86_6444.1.9
freedos-1.3x861.3
home-assistantaarch6417.3.1
kalix86_642026.1.1
nixos-25.11x86_6425.11.1
opensuse-leapx86_6416.0
opensuse-tumbleweedx86_6420260613
parrotx86_647.2.1
rocky-9x86_649.8.1
templeosx86_645.03.1
ubuntu-24.04aarch64, riscv64, x86_64aarch64 24.04.20260520, riscv64 24.04.20260520, x86_64 24.04.20260519
ubuntu-26.04x86_6426.04.1
windows-10x86_6419045.2006.5
windows-11x86_6426100.1742.3
windows-server-2019x86_6417763.737.5
windows-server-2022x86_6420348.169.5
windows-server-2025x86_6426100.1742.6

The Windows images are evaluation editions with their 180-day licences. Their first boot replays Windows specialize before the VM reports ready.