The Problem: I had been manually creating Ubuntu Virtual Machines within my Proxmox Home Lab. This included looking through the UI, uploading the Cloud Image, re-typing the same SSH keys multiple times, etc. The process was time consuming, and prone to human error and it was impossible to automate or replicate. Each Virtual Machine I created was a "15-minute job".
The Constraints: My goal was to create an entirely Infrastructure as Code-based workflow that would have no dependency upon PXE boot or any large orchestration tools. Only one node on my Proxmox was to be used, and I wanted each VM to be fully configured (hostname, user, sshkey and packages) upon their initial start; I also wanted to configure the VMs without ever having to log into the console.
The Solution: I am using Terraform with the Telmate Proxmox provider, in addition to cloud-init templates. Once I create my template, I can create a fully functioning VM in under a minute. Within this guide, I will describe the exact configurations I have used, the mistakes that I made which resulted in losing many hours of my time, and the checks I have established to ensure that a similar situation does not happen again.
Quick Summary
Provision Ubuntu 22.04 Cloud-Init VMs on Proxmox VE v8.2.
Use a dedicated API Token with minimum privileges assigned — Not ROOT.
Attach a Cloud-Init ISO to the ide2, rather than a SCSI slot; otherwise, your Clone will fail without warning.
Pin your providers to specific versions and use variables so you can spawn many VMs off a single Root Module.
Tested On: Proxmox VE v8.2 (kernel 6.8.12-2-pve), Terraform v1.8.4, Ubuntu 22.04 LTS Cloud Image. Everything outlined in this document was tested in my single-node Homelab using Local Disk Storage.
In order to allow Terraform to interact with your Proxmox virtual machines you must prepare four items prior to beginning: a clean cloud-init template; a Proxmox API token with restricted permissions; selecting where to store and how to track your resource state; and setting the pinned versions for your Terraform templates.
Prerequisites & Planning
Prepare the Proxmox Template Clone
To create your Ubuntu cloud image template follow these steps: Download the Ubuntu Cloud Image from the official website and run it on a new tiny virtual machine. An agent must be installed in the virtual machine so that Terraform can receive the IP address via cloud-init and continue executing the command successfully. Do not modify the template or configuration of the image without creating a backup!
If you already have a VM you’re turning into a template, clone it first
qm clone 9000 9999 --name backup-before-template --full
You may want to keep a second shell session open on your Proxmox host while executing commands for backup and recovery.
Download the image and import it into a new virtual machine.
wget "
pm_api_token_id = var.proxmox_token_id
pm_api_token_secret = var.proxmox_token_secret
pm_tls_insecure = true # only for homelabs with self‑signed certs
}
This establishes the Provider and locks it to Version 2.9.14. Use the Terraform Registry page for the Proxmox provider as a point of reference whenever your Provider Versions begin to drift from Version 2.9.14.
You can initialize your Provider and verify connectivity by using the following command:
terraform init
terraform plan
Crafting the Terraform Proxmox QEMU Resource with Cloud-Init Drive
Now let's look at the VM Resource. This is where you will determine whether your Cloud-Init drive mapping is functional or not. This code is placed in main.tf:
resource "proxmox_vm_qemu" "cloudinit_vm" {
name = "lab-ubuntu-01"
target_node = "pve"
clone = "ubuntu-2204-cloudinit-template"
full_clone = false
cores = 2
memory = 2048
sockets = 1
scsihw = "virtio-scsi-single"
disk {
type = "scsi"
storage = "local-lvm"
size = "20G"
}
network {
model = "virtio"
bridge = var.vm_network_bridge
}
# Cloud‑init drive MUST be on ide2
cloudinit_cdrom_storage = "local-lvm"
os_type = "cloud-init"
sshkeys = var.ssh_public_key
ciuser = var.vm_user
ipconfig0 = "ip=dhcp"
}
The first line of cloudinit_cdrom_storage tells Terraform to create a Cloud-Init ISO on the target storage and attach it to this ide2. If you do not include this line, or you try to put a Cloud-Init drive in scsi0, Terraform will still create a clone of your VM; however, the Cloud-Init data will be silently ignored. I will go into more detail about this point in the Pitfalls.
You can read about why the Cloud-Init Drive must appear as a CD-ROM drive in the cloud‑init NoCloud datasource reference. As specified, the NoCloud data source will require the following volume label: cidata, using Proxmox to handle that for you.
The following commands will help you run a plan so you know exactly what Terraform is going to do with all of your hardware, including the creation of the cloud-init drive from scratch.
$ terraform plan
- resource "proxmox_vm_qemu" "cloudinit_vm" {
- name = "lab-ubuntu-01"
- target_node = "pve"
- clone = "ubuntu-2204-cloudinit-template"
- scsihw = "virtio-scsi-single"
- cloudinit_cdrom_storage = "local-lvm" <-- cloud‑init ISO created here
- ipconfig0 = "ip=dhcp"
- sshkeys = (sensitive value)
...
}
Using the above commands, you will see that all of the entries listed above will be created by Terraform when you run the plan.
Running and Verifying the Automated Deployment
You should create a backup of the Proxmox node configuration before applying the configuration. At minimum, dump your running VM list with qm list so you have a record to revert to if the state gets tangled. Also, you should have a second root shell open in the Proxmox host.
To apply the configuration:
$ terraform apply --auto-approve
proxmox_vm_qemu.cloudinit_vm: Creating...
proxmox_vm_qemu.cloudinit_vm: Still creating... [10s elapsed]
proxmox_vm_qemu.cloudinit_vm: Creation complete after 15s [id=qemu/100]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Outputs:
vm_ip = 192.168.10.143 <-- that’s your new VM ready for SSH
Once the stack has been applied, you will want to verify that cloud-init has finished running on the VM:
ssh .
If in doubt, check the provider logs using TF_LOG=DEBUG terraform apply to see if you can find an error message like “unable to create cloudinit drive”.
Frequently Asked Questions
Why does the Terraform Proxmox provider return a “401 permission denied” error when I use the correct API token?
The token path is case-sensitive and must match User@realm!tokenid exactly. The token must also have Sys.Audit assigned to / to allow the provider to enumerate the nodes. Once you provide that permission to the token, this error will no longer occur.
How can I automatically increase the size of the cloud-init disk after cloning my template to create a VM?
Although you cannot increase the size of the cloud‑init ISO itself, you can increase the size of the root disk by using a remote-exec provisioner that runs growpart and resize2fs. Make sure your cloudinit user-data file contains growpart under bootcmd or a runcmd. It is recommended not to use provisioner blocks for any heavy configuration. They should be considered a last resort.
Can I use one Terraform configuration to operate on multiple Proxmox nodes in a home lab?
Yes! You can configure provider aliases for each node, and use a loop to iterate over a map of endpoints. Be mindful of the API's concurrency limits; keep parallelism low (for example, -parallelism=2), so you do not overwhelm your host with too many requests at once. While looping over nodes using for_each works, make sure you validate the token on each node before executing the configuration.
SOCIAL SHARE CARD GENERATOR