Remote Support Start download

Proxmox Templates + Cloud-Init: A Practical Workflow

ProxmoxCloud-InitAutomation
Proxmox Templates + Cloud-Init: A Practical Workflow

Anyone rolling out Proxmox VMs on a regular basis knows the ritual: mount the ISO, click through the installer, add the SSH key, configure networking, install packages. Ten minutes per machine when everything goes smoothly. On a Kubernetes cluster with six nodes that quickly turns into two hours — and every machine ends up subtly different, because manual clicks never repeat identically.

The combination of Proxmox templates and Cloud-Init solves exactly that problem. Instead of an ISO installer you use the official distribution cloud images as the golden base, clone them in seconds, and hand instance-specific parameters — hostname, IP, SSH key, user — to the VM via a Cloud-Init drive. This article walks through the complete workflow we use in customer projects and includes the Bash script that rolls out ten VMs in under 60 seconds.

Why Cloud-Init instead of a self-maintained golden image?

The classic approach is a self-curated golden image: install once, add all packages, shut down, mark as template. Works fine — until the image ages. After three months security patches are missing, kernel versions drift apart, and the next update cycle forces the process to start from scratch.

Cloud images are the more elegant path. Debian, Ubuntu, Rocky Linux, AlmaLinux and openSUSE all publish minimal, qcow2-optimized images that are maintained by the distributions themselves and refreshed weekly. They come with the cloud-init package pre-installed and read their configuration on first boot from an attached virtual CD-ROM. Proxmox integrates with exactly this mechanism through its built-in Cloud-Init support.

For pure provisioning without application logic (base OS, user, SSH, network) Cloud-Init is entirely sufficient. For more complex configuration — Kubernetes join, Ansible bootstrap, compliance baselines — combine Cloud-Init with a post-provisioning tool. Cloud-Init brings the machine online and hands off to Ansible, Salt or an in-house script.

Step 1: Download the cloud image and build the template

The starting point is a current cloud image. For Debian 13 (Trixie) it looks like this — executed on a Proxmox 8.x host as root:

# Download the current Debian 13 cloud image
cd /var/lib/vz/template/iso
wget https://cloud.debian.org/images/cloud/trixie/latest/debian-13-generic-amd64.qcow2

# Create the VM shell (we conventionally reserve ID 9000 for templates)
qm create 9000 --name debian-13-tpl --memory 2048 --cores 2 \
  --net0 virtio,bridge=vmbr0 --scsihw virtio-scsi-single

# Import the cloud image as the root disk
qm importdisk 9000 debian-13-generic-amd64.qcow2 local-zfs

# Attach the imported disk as scsi0 and set the boot order
qm set 9000 --scsi0 local-zfs:vm-9000-disk-0,discard=on,ssd=1
qm set 9000 --boot order=scsi0 --serial0 socket --vga serial0

# Attach a Cloud-Init drive as a CD-ROM (Proxmox generates the ISO on the fly)
qm set 9000 --ide2 local-zfs:cloudinit

# Convert to a template
qm template 9000

Three details matter here: discard=on enables TRIM/Unmap pass-through, important for thin-provisioned ZFS or Ceph. serial0 makes sure cloud images without a graphics console send their boot logs to the serial console — skip this and the first boot shows a black screen. And virtio-scsi-single gives every disk its own controller, which is cleaner for parallel I/O and later hotplug operations.

Step 2: Cloning with Cloud-Init parameters

The template itself is never booted. Every production VM is created via qm clone and then receives its individual Cloud-Init values:

# Full clone of template 9000 to new VM 101
qm clone 9000 101 --name web01 --full --storage local-zfs

# Set Cloud-Init parameters
qm set 101 --ciuser deploy \
  --sshkeys ~/.ssh/authorized_keys \
  --ipconfig0 ip=10.10.20.11/24,gw=10.10.20.1 \
  --nameserver 10.10.20.1 \
  --searchdomain internal.example.com

# Grow the disk to the target size (cloud images are usually 2-4 GB)
qm resize 101 scsi0 +30G

# Adjust resources and start
qm set 101 --memory 4096 --cores 4
qm start 101

After 15 to 25 seconds the VM is reachable via SSH — without you ever having seen a console. On first boot Cloud-Init read hostname, network, user and SSH key from the virtual ISO, expanded the filesystem to the new disk size and then disabled itself.

Step 3: Ten VMs in under 60 seconds

The jump from one VM to ten is a Bash loop. The following script rolls out a Kubernetes worker pool:

#!/usr/bin/env bash
set -euo pipefail

TEMPLATE_ID=9000
STORAGE=local-zfs
BRIDGE=vmbr0
GW=10.10.20.1
DNS=10.10.20.1
SSH_KEYS=/root/.ssh/deploy_authorized_keys
DOMAIN=internal.example.com

for i in $(seq 1 10); do
  VMID=$((200 + i))
  IP="10.10.20.$((100 + i))"
  NAME="k8s-worker-$(printf '%02d' "$i")"

  qm clone "$TEMPLATE_ID" "$VMID" \
    --name "$NAME" --full --storage "$STORAGE" &

done
wait

for i in $(seq 1 10); do
  VMID=$((200 + i))
  IP="10.10.20.$((100 + i))"

  qm set "$VMID" \
    --ciuser deploy \
    --sshkeys "$SSH_KEYS" \
    --ipconfig0 "ip=${IP}/24,gw=${GW}" \
    --nameserver "$DNS" \
    --searchdomain "$DOMAIN" \
    --memory 8192 --cores 4
  qm resize "$VMID" scsi0 +50G
  qm start "$VMID"
done

The trick: the ten qm clone calls run in parallel in the background. On a host with NVMe or Ceph storage a full clone of a 2 GB image finishes in a few seconds — in our customer projects we typically see 40 to 55 seconds from launching the script to SSH login on the last VM. Anyone using linked clones (possible with some storage types) gets there faster still, but at the cost of independence from the template.

When to use template + Cloud-Init — and when not

Not every situation benefits from the Cloud-Init approach. The following table summarizes when the setup effort pays off and when other tools are a better fit:

ScenarioRecommendationReason
Recurring Linux VMs, same patternTemplate + Cloud-InitSeconds instead of minutes per rollout
Windows VMsTemplate + Sysprep + Unattended.xmlCloud-Init on Windows works, but is less mature
A single legacy serverClassic ISO installerSetup overhead not worthwhile
Full cluster with app stackCloud-Init + Ansible/TerraformCloud-Init bootstraps, Ansible configures
Immutable approach, many rebuildsPacker + Cloud-InitPacker builds versioned templates automatically
Very heterogeneous workloadsIndividual templates per roleFewer qm set parameters in the script

In practice we almost always recommend a two-layer strategy in Proxmox consulting: one or two cleanly maintained Cloud-Init templates (Debian LTS, Ubuntu LTS, optionally Rocky) plus an Ansible playbook that handles role-specific post-configuration. That keeps the template count small, makes updates trivial (swap the image, rebuild the template) and cleanly separates OS provisioning from application logic.

Pitfalls from the field

Three problems keep coming up in customer projects:

Missing qemu-guest-agent integration. Cloud images often ship without the agent. Add it to the template via virt-customize -a debian-13-generic-amd64.qcow2 --install qemu-guest-agent (from the libguestfs-tools package), otherwise Proxmox won’t show an IP in the GUI and backups can’t trigger filesystem freezes.

Wrong storage type for the Cloud-Init drive. The Cloud-Init ISO has to live on a storage that supports snippets or iso. On pure images storages (in some Ceph pool configurations) qm set --ide2 ... :cloudinit will fail. Fix: create a second storage entry with content=iso,vztmpl,snippets.

Static IPs collide after cloning. If you booted the template for testing and didn’t clean up, Proxmox clones the old machine ID along with it. That leads to duplicate DHCP leases and broken SSH host key behavior. So: never boot the template, always clone only. If you really must — run cloud-init clean --logs and truncate -s 0 /etc/machine-id before conversion.

Conclusion

Templates plus Cloud-Init turn Proxmox from a point-and-click hypervisor into a provisioning platform. The one-time effort of building a clean template — an hour including testing — pays off on the second rollout. Combined with Ansible or Terraform it produces a reproducible, versionable deployment procedure that works even in small IT departments without a dedicated DevOps team.

DATAZONE supports you in adopting this workflow — from template design through storage and network architecture to integration with existing backup and monitoring landscapes. If you want to move your Proxmox environment out of manual mode and into automated infrastructure, get in touch: Contact us.

More on these topics:

Need IT consulting?

Contact us for a no-obligation consultation on Proxmox, OPNsense, TrueNAS and more.

Get in touch