Proxmox Cloud-Init Templates: Automated VM Deployment
Create Proxmox cloud-init VM templates for automated deployment with SSH keys, network config, and user setup. Clone production-ready VMs in seconds.
On this page
If you're still installing VMs from ISO files, clicking through the installer, and manually configuring networking on each one, you're doing it the hard way. I used to spend 20 minutes per VM just getting a base system ready. Now I clone a cloud-init template and have a fully configured, SSH-accessible VM in under 30 seconds.
Cloud-init is the industry standard for instance initialization. AWS, GCP, Azure — they all use it. And Proxmox has native support built right into the GUI and CLI. Once you set up a proper template, every VM you deploy comes with your SSH keys, correct network settings, hostname, and user account pre-configured. No manual intervention needed.
This guide walks through the entire process: downloading cloud images, converting them into Proxmox templates, configuring cloud-init parameters, and cloning VMs at scale. I'll cover both Ubuntu and Debian since those are what most people run.
How Cloud-Init Works on Proxmox
Cloud-init runs during the first boot of a VM. Proxmox passes configuration data through a virtual CD-ROM drive (a NoCloud datasource) that cloud-init reads automatically. The VM picks up its hostname, network config, SSH keys, and any custom scripts you've defined — all without you touching a console.
The flow looks like this:
- You create a VM template with a cloud-init-enabled disk image
- You set cloud-init parameters (user, SSH keys, IP config)
- You clone the template to create a new VM
- The new VM boots, cloud-init reads the config, applies everything, and you SSH in
Proxmox stores cloud-init config separately from the disk, so you can change parameters per-clone without modifying the base template. That's the key advantage over just snapshotting a configured VM.
Downloading Cloud Images
Cloud images are minimal OS installations designed specifically for cloud-init. They're pre-built, small (usually 300-700MB), and boot fast because there's no installer — the OS is already laid down on disk.
Ubuntu Server
Ubuntu publishes official cloud images at https://cloud-images.ubuntu.com. Grab the latest LTS:
wget https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img
That gives you Ubuntu 24.04 LTS (Noble Numbat). The .img file is a QCOW2 disk image ready for import.
Debian
Debian's cloud images live at https://cloud.debian.org/images/cloud/:
wget https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2
Rocky Linux (RHEL Alternative)
If you need a RHEL-compatible distro:
wget https://dl.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud.latest.x86_64.qcow2
Store these somewhere accessible on your Proxmox host. I keep mine in /var/lib/vz/template/iso/ for organization, though they're not ISO files.
Creating the VM Template
This is where most tutorials get confusing. You're not installing an OS — you're creating an empty VM, importing the cloud image as its disk, attaching a cloud-init drive, and then converting the whole thing to a template. Let me break it down step by step.
Step 1: Create the Base VM
qm create 9000 --name ubuntu-cloud --memory 2048 --cores 2 \
--net0 virtio,bridge=vmbr0 \
--agent enabled=1 \
--ostype l26
VM ID 9000 is conventional for templates — it keeps them visually separated from regular VMs in the GUI. The --agent enabled=1 flag tells Proxmox to expect the QEMU guest agent, which gives you proper IP reporting and clean shutdowns.
Step 2: Import the Cloud Image as a Disk
qm importdisk 9000 noble-server-cloudimg-amd64.img local-lvm
This converts the cloud image and imports it into your storage. Replace local-lvm with whatever storage backend you're using (ZFS, Ceph, NFS, etc.).
The command outputs the disk name — something like local-lvm:vm-9000-disk-0. You need to attach it:
qm set 9000 --scsihw virtio-scsi-pci --scsi0 local-lvm:vm-9000-disk-0
Step 3: Add a Cloud-Init Drive
qm set 9000 --ide2 local-lvm:cloudinit
This creates a small virtual CD-ROM that Proxmox will populate with your cloud-init configuration. Cloud-init inside the VM detects this drive automatically on first boot.
Step 4: Set the Boot Order
qm set 9000 --boot order=scsi0
Without this, the VM might try to PXE boot or boot from the cloud-init CD instead of the actual disk.
Step 5: Enable Serial Console (Optional but Recommended)
qm set 9000 --serial0 socket --vga serial0
This gives you console access through Proxmox even if networking fails during first boot. Invaluable for debugging cloud-init issues.
Step 6: Resize the Disk
Cloud images ship with tiny root partitions (usually 2-3GB). Resize before templating:
qm resize 9000 scsi0 +28G
This gives you a 30GB root disk. Cloud-init will automatically grow the filesystem partition to fill the available space on first boot — no manual growpart needed.
Configuring Cloud-Init Parameters
Now the fun part. You can set these through the Proxmox GUI (Hardware > Cloud-Init tab) or via CLI. I prefer CLI because it's scriptable.
User and SSH Keys
qm set 9000 --ciuser admin
qm set 9000 --sshkeys ~/.ssh/id_ed25519.pub
The --ciuser creates the default user account. The --sshkeys flag accepts a file path containing public keys (one per line). Cloud-init will install these in the user's ~/.ssh/authorized_keys.
Want to set a password too? You can, but you shouldn't for production:
qm set 9000 --cipassword $(openssl passwd -6 yourpassword)
Network Configuration
For DHCP (simplest):
qm set 9000 --ipconfig0 ip=dhcp
For static IP (better for servers):
qm set 9000 --ipconfig0 ip=10.0.10.100/24,gw=10.0.10.1
qm set 9000 --nameserver 10.0.10.1
qm set 9000 --searchdomain homelab.local
Each clone can override these values, so the template settings are just defaults.
DNS Configuration
qm set 9000 --nameserver "10.0.10.1 1.1.1.1"
qm set 9000 --searchdomain homelab.local
Converting to a Template
Once everything is configured:
qm template 9000
That's it. The VM becomes a template. It shows up with a different icon in the GUI, and you can no longer start it directly. This is a one-way operation — you can't un-template a VM. If you need to modify the template, clone it first, make changes, then create a new template.
Cloning VMs from the Template
Quick Clone (Linked)
qm clone 9000 200 --name web-server-01
qm set 200 --ipconfig0 ip=10.0.10.200/24,gw=10.0.10.1
qm start 200
Linked clones share the base template's disk and only store differences. They're fast to create and use less storage, but you can't delete the template while linked clones exist.
Full Clone
qm clone 9000 201 --name db-server-01 --full
qm set 201 --ipconfig0 ip=10.0.10.201/24,gw=10.0.10.1
qm set 201 --memory 8192 --cores 4
qm start 201
Full clones are completely independent copies. They use more storage but have no dependency on the template. For production workloads, I always use full clones.
Batch Deployment Script
Here's a script I use to deploy multiple VMs at once:
#!/bin/bash
TEMPLATE=9000
BRIDGE=vmbr0
GATEWAY=10.0.10.1
NAMESERVER=10.0.10.1
declare -A VMS=(
[210]="k8s-control-01:10.0.10.210:4096:4"
[211]="k8s-worker-01:10.0.10.211:8192:4"
[212]="k8s-worker-02:10.0.10.212:8192:4"
[213]="k8s-worker-03:10.0.10.213:8192:4"
)
for VMID in "${!VMS[@]}"; do
IFS=':' read -r NAME IP MEM CORES <<< "${VMS[$VMID]}"
echo "Creating $NAME (VMID: $VMID, IP: $IP)"
qm clone $TEMPLATE $VMID --name "$NAME" --full
qm set $VMID --memory "$MEM" --cores "$CORES"
qm set $VMID --ipconfig0 "ip=${IP}/24,gw=${GATEWAY}"
qm set $VMID --nameserver "$NAMESERVER"
qm start $VMID
echo "$NAME started successfully"
done
Four VMs deployed and running in under two minutes. Try doing that with manual installs.
Installing the QEMU Guest Agent
Cloud images don't always include the QEMU guest agent, and you want it. It gives Proxmox visibility into the VM's IP addresses, enables clean shutdown commands, and allows filesystem freeze for consistent backups.
Add this to your cloud-init config by creating a snippet. First, enable the snippets storage:
pvesm set local --content vztmpl,backup,iso,snippets
Then create a cloud-init vendor config:
# /var/lib/vz/snippets/vendor-data.yaml
#cloud-config
package_update: true
packages:
- qemu-guest-agent
runcmd:
- systemctl enable qemu-guest-agent
- systemctl start qemu-guest-agent
Attach it to your template before converting:
qm set 9000 --cicustom "vendor=local:snippets/vendor-data.yaml"
Now every VM cloned from this template will automatically install and start the guest agent on first boot.
Troubleshooting Common Issues
VM Boots but Cloud-Init Doesn't Run
Check that the cloud-init drive is attached and the cloud-init package is installed in the image. You can verify by mounting the cloud image:
modprobe nbd
qemu-nbd --connect=/dev/nbd0 noble-server-cloudimg-amd64.img
mount /dev/nbd0p1 /mnt
ls /mnt/etc/cloud/
umount /mnt
qemu-nbd --disconnect /dev/nbd0
If /etc/cloud/ exists and has config files, cloud-init is installed.
SSH Connection Refused
Cloud-init might still be running. It takes 30-60 seconds on first boot, depending on whether it's running package updates. Check the console output:
qm terminal 200
Look for Cloud-init v. XX.X finished in the output. If you see errors, check:
cat /var/log/cloud-init-output.log
cat /var/log/cloud-init.log
IP Address Not Showing in Proxmox
This means the QEMU guest agent isn't running. Inside the VM:
systemctl status qemu-guest-agent
If it's not installed, install it manually and add it to your vendor data for future clones.
Disk Not Growing to Full Size
Cloud-init uses growpart to expand the partition. If it fails, the root partition stays tiny. Inside the VM:
growpart /dev/sda 1
resize2fs /dev/sda1 # for ext4
# or
xfs_growfs / # for xfs
Template Maintenance
Templates aren't set-and-forget. Cloud images get security updates, and you should refresh your templates monthly:
- Download the latest cloud image
- Clone your existing template to a regular VM
- Import the new disk image to the clone
- Reconfigure cloud-init settings
- Convert the clone to a new template
- Test with a quick clone
- Delete the old template once verified
I keep a simple cron job that downloads fresh images weekly:
# /etc/cron.weekly/update-cloud-images
#!/bin/bash
wget -q -N -P /var/lib/vz/template/iso/ \
https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img
The -N flag only downloads if the remote file is newer, so it's bandwidth-friendly.
Integrating with Terraform
Once you have templates, the next logical step is infrastructure as code. Proxmox has a Terraform provider that works directly with cloud-init templates:
resource "proxmox_vm_qemu" "web_server" {
name = "web-server-01"
target_node = "pve1"
clone = "ubuntu-cloud"
full_clone = true
cores = 2
memory = 4096
os_type = "cloud-init"
ipconfig0 = "ip=10.0.10.100/24,gw=10.0.10.1"
nameserver = "10.0.10.1"
ciuser = "admin"
sshkeys = file("~/.ssh/id_ed25519.pub")
disk {
storage = "local-lvm"
size = "50G"
}
}
This gives you version-controlled, repeatable infrastructure that you can terraform plan before applying. Combined with cloud-init templates, you can rebuild your entire lab from scratch in minutes.
Conclusion
Cloud-init templates are the single biggest quality-of-life improvement you can make to your Proxmox workflow. The upfront investment is maybe 30 minutes to set up your first template. After that, every VM deployment goes from a 20-minute manual process to a 30-second clone-and-start operation.
Start with one template — Ubuntu or Debian, whichever you use most. Get the guest agent baked in with vendor data. Set up your SSH keys. Then start cloning. Once you experience the speed difference, you'll wonder how you ever tolerated manual installs.
For larger environments, pair templates with Terraform and you've got yourself an infrastructure pipeline that rivals what the cloud providers offer — except it's running on hardware you own, in your own rack.