Skip to content

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.

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.

  1. Start from the distribution’s cloud image.
  2. Install the QEMU guest agent.
  3. Leave cloud-init enabled and reset (cloud-init clean).
  4. 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.

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.

Terminal window
./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>.qcow2

The build script enforces these with gates that stop the build rather than producing a subtly broken template:

RequirementWhy
No openssh-serverAppliances cannot be logged into
No snapdNothing self-updates outside the platform’s own updater
qemu-guest-agent presentAddress readback and lifecycle
The daemon binary and its updaterThe machine converges on its own
A per-boot hook for networkingRoutes are re-applied every boot rather than configured once
No cluster secret in the imageA secret baked into a template is in every machine cloned from it
Console logging only, no gettyDiagnosis without a login

cloud-init clean --logs --machine-id before converting to a template. Without it every clone shares a machine identity.

Clone it once, boot it, and check the gates:

Terminal window
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> ping

Then 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.

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.

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.