Docs / Operations

Building your own template

A template is a ZFS dataset with a raw disk in it — nothing more. Making one by hand, what the image has to support, publishing a catalogue of your own, and keeping templates in step across a fleet.

Guests are ZFS clones of a template, which is why a deploy takes under a second. The published catalogue covers the common distributions — but sooner or later you want a golden image with your packages, your users and your agent already in it.

The good news is that a template is a much smaller idea than it sounds.

A template is a dataset with a disk in it

That is the whole specification:

Output
<vm dataset>/template-<name>/disk0.img

A ZFS dataset whose name begins with template-, containing a raw disk image called disk0.img. Nothing else — no manifest, no metadata file, no registration step. Create that and hoster vm deploy --os-template <name> works.

hoster vm template list           # what this node has
hoster vm template list --json

Let the tool write the recipe

pull has a --debug flag that prints the shell commands instead of running them — which makes it the authoritative, self-updating description of the layout:

hoster vm template pull almalinux9 --debug
Output
#================================================#
# Review the following commands before executing #
# Then pipe the output to the shell              #
#================================================#

cd /opt/hoster/tmp
wget https://<image server>/images/almalinux9.zip
unzip almalinux9.zip
zfs create tank/hoster/vm-encrypted/template-almalinux9
mv disk0.img tank/hoster/vm-encrypted/template-almalinux9/disk0.img

Four commands. Download, unpack, create the dataset, move the disk in. Building your own template means producing that disk0.img and doing the last two steps yourself.

--debug is the legacy flow, not the recommended one. Ordinary pull downloads, extracts and installs by itself, resumes, and skips the download when the local copy is already current. Use --debug to read the procedure, not as the way to pull an official image.

Making the disk

The image has to be raw — not qcow2, not vmdk. Convert whatever you have:

qemu-img convert -f qcow2 -O raw source.qcow2 disk0.img

Then install it:

zfs create tank/hoster/vm-encrypted/template-mybase
mv disk0.img /tank/hoster/vm-encrypted/template-mybase/disk0.img
hoster vm template list
hoster vm deploy --os-template mybase --name test-01 --start-when-ready

Deploy something from it immediately. A template that clones but does not boot is a problem you want to find now, not the first time you need it in a hurry.

What the image must support

Hoster configures a guest through cloud-init, delivered as an ISO built with genisoimage and labelled cidata. So the image needs:

Requirement Why
cloud-init installed it is how the hostname, users, passwords, SSH keys and network configuration get in
the NoCloud datasource enabled the configuration arrives on an attached cidata volume, not from a metadata service
a serial console on ttyS0 hoster vm console is a serial connection; without it you get a blank screen
the root filesystem growable the disk can be expanded at deploy or later, but the guest grows its own partition
a raw disk0.img the clone-and-boot path assumes it

The first three are the ones that bite. An image built for a cloud that uses a metadata service will boot and then be unreachable, because nothing configured its network or installed your key — and with no serial console you cannot see why.

Most distributions’ generic cloud images already satisfy all of this, which makes them the right starting point. Build from those rather than from an ISO install where you can.

Consider whether the base image runs growpart/cloud-initramfs-growroot on first boot. If it does, a guest deployed with --os-disk-size larger than the template comes up with the space already usable. If not, growing the filesystem is manual every time — see the guest still has work to do.

A note on golden images

Whatever you bake in is baked in for every guest cloned from it. Two things to strip before you call an image finished:

  • Machine identity/etc/machine-id, host SSH host keys, and cloud-init’s own state under /var/lib/cloud. Left in place, every guest is the same machine as far as anything that reads them is concerned.
  • Anything secret. A template is cloned, replicated across the cluster, and possibly published. Credentials in it travel with it.
Screenshot

Take a screenshot of `hoster vm template list` showing a mix of pulled official templates and a custom one, with sizes, so the custom template looks like an ordinary member of the list.

Publishing a catalogue of your own

hoster vm template list-online and pull read an image server — a plain HTTP server returning a JSON index at its root, with the archives underneath /images/:

Output
GET https://images.example.com/            → the catalogue, as JSON
GET https://images.example.com/images/<archive>

The catalogue is keyed under vm_images, mapping each template name to its archive:

Output
{
  "vm_images": [
    { "almalinux9": ["almalinux9.zip", ...] },
    { "mybase":     ["mybase.zip", ...] }
  ]
}

Archives are zip, and must contain disk0.img — that is what the extractor looks for. Serving your own catalogue is therefore a static file server plus a JSON index, with no Hoster-specific software on it.

Point a node at yours by setting the image servers in the host configuration:

There is no CLI for the image server list. HostSetVmImageServers exists in HosterLib and the agent exposes it, but no hoster command sets it. Changing where a node pulls images from means Control Center, the API, or editing host_config.json.

Only the first configured image server is ever used. vm_image_servers is a list, and both list-online and pull read servers[0] and ignore the rest. It is not a fallback chain or a mirror list, whatever the plural name suggests — a second entry is dead configuration.

If you would rather not host a catalogue at all, --link-only prints the URL so you can fetch through your own mirror and install the disk by hand:

hoster vm template pull almalinux9 --link-only

Getting a template onto every node

A template is per-dataset and per-host, so a fleet needs it distributed. Three mechanisms, in increasing order of automation:

hoster vm template sync --check          # between datasets on this host
hoster vm template sync --template mybase
hoster vm template replicate --check     # from cluster peers to this host
hoster vm template replicate --ratelimit 200

sync copies between the VM datasets of one host — useful when you have both an encrypted and an unencrypted dataset and want the template in each. replicate pulls from cluster peers, so a template that exists anywhere in the cluster can be brought here without going back to the image server.

Both take --check to print the plan without moving anything, and --template to limit the operation to one name. sync also has --debug, which prints commands rather than running them, in the same spirit as pull --debug.

In a cluster the scheduler runs template replication hourly, so this is usually already happening — see the scheduler. These are the manual equivalents, for when you have just built something and do not want to wait.

Excluding a template from replication

hoster vm template set-config replicate-set-false mybase
hoster vm template set-config replicate-set-true  mybase

Worth using for a large template only one node needs, or one you are still iterating on and would rather not push across the fleet on every hourly run.

MicroVM templates

The same idea, a different dataset type: microVM templates live in container datasets rather than VM datasets, and have their own catalogue and their own commands.

hoster microvm template list-online
hoster microvm template pull debian13
hoster microvm template list
hoster microvm template sync --check
hoster microvm template replicate --check

hoster microvm template pull has no --debug. The VM version prints the shell commands instead of running them; the microVM version does not, so there is no equivalent self-documenting recipe for the container-dataset layout. Build one by copying the VM procedure and substituting a container dataset.

See MicroVMs for what a microVM template contains and why a compose workload expects /data.

When a custom template does not work

  1. Does it appear? hoster vm template list. If not, the dataset name does not start with template-, or it is not under a VM dataset.
  2. Does it deploy? A clone failure is a ZFS or naming problem, not an image problem.
  3. Does it boot? hoster vm console <name> — if you see nothing at all, the image has no serial console on ttyS0 and you are debugging blind. Fix that first.
  4. Does it get an address and your key? That is cloud-init. Check the datasource is NoCloud and that cloud-init is actually installed and enabled.
  5. Is the disk raw? qemu-img info disk0.img should say raw.

Next

  • Your first VM — the deploy side, and the flags a template’s consumers will use.
  • MicroVMs — the other template type.
  • The scheduler — the hourly fan-out that distributes what you build.

Something unclear on this page?

Ask about this specific page and we will come back to you. Your question arrives with a link to it, so you do not have to describe where you were.