6. Machine images
An image is a Proxmox template. The catalogue maps a slug — ubuntu-24.04,
windows-2025-std, lb — to a template identifier per region in the
region configuration.
Two families
Section titled “Two families”Customer images are what users pick when creating a server: Ubuntu, Windows, and whatever else you offer. They have cloud-init or cloudbase-init, the QEMU guest agent, and nothing else opinionated.
Appliance images back load balancers, gateways and storage nodes. Users never choose
them. They are locked: no SSH daemon, no login shell, no user accounts, snapd
removed. See Appliances and the pull model.
Building a customer image
Section titled “Building a customer image”- Start from the distribution’s cloud image.
- Install the QEMU guest agent.
- Leave cloud-init enabled and reset (
cloud-init clean). - Convert to a template.
Windows needs more: install cloudbase-init as a system service with its network plugin
enabled, install the guest agent from its own installer, then run sysprep with
/generalize /oobe /shutdown and an unattend file. Getting the network plugin wrong
produces a machine that boots and has no address — a failure that looks like a networking
problem and is not.
Building an appliance image
Section titled “Building an appliance image”The repository ships a build script. It has two phases: build, which produces a disk image, and import, which turns it into a template on a node.
./deploy/guest/build-template.sh build <gateway|lb|s3>scp /var/tmp/zone9-templates/z9-<kind>.qcow2 root@<node>:/var/tmp/./deploy/guest/build-template.sh import <vmid> /var/tmp/z9-<kind>.qcow2What every appliance image must have
Section titled “What every appliance image must have”The build script enforces these with gates that stop the build rather than producing a subtly broken template:
| Requirement | Why |
|---|---|
No openssh-server | Appliances cannot be logged into |
No snapd | Nothing self-updates outside the platform’s own updater |
qemu-guest-agent present | Address readback and lifecycle |
| The daemon binary and its updater | The machine converges on its own |
| A per-boot hook for networking | Routes are re-applied every boot rather than configured once |
| No cluster secret in the image | A secret baked into a template is in every machine cloned from it |
| Console logging only, no getty | Diagnosis without a login |
cloud-init clean --logs --machine-id before converting to a template. Without it every
clone shares a machine identity.
Verifying a template
Section titled “Verifying a template”Clone it once, boot it, and check the gates:
qm clone <template> <test-vmid> --full --name verify --storage <storage>qm set <test-vmid> --net0 virtio,bridge=<bridge>,tag=<vlan> \ --ipconfig0 ip=<addr>/24,gw=<gw> --nameserver <resolver>qm start <test-vmid> && sleep 60 && qm agent <test-vmid> pingThen confirm from inside: the daemon reports the expected version, its unit is
disabled (identity arrives at bootstrap, not in the image), no snapd, no
openssh-server, and no cluster configuration file present.
Destroy the test machine afterwards.
Publishing to the catalogue
Section titled “Publishing to the catalogue”Add the template identifier to the region’s images map and enable the catalogue entry.
An image with no template in a region does not appear there — which is how you stage a
rollout region by region.
Updating an image
Section titled “Updating an image”Build a new template with a new identifier and point the region at it. Existing machines are unaffected; they were cloned, not linked. Old templates can be removed once nothing references them.