How to Deploy Your First Proxmox Virtual Machine Using Terraform

🧠 Why Terraform for Proxmox?
While Proxmox has a great web UI, infrastructure-as-code lets you:
Automate repeatable VM deployments
Keep configurations under version control
Easily spin up multi-VM Setups
Reduce human error
👋 Quick heads-up!
This guide is Part 2 of a multi-part series.
In this article, we’ll walk through How to Deploy a Virtual Machine in proxmox Using Terraform
👉 Jump to Part 1: How to Set Up Proxmox with Terraform →
📦 Prerequisites
Before you begin, make sure you have:
✅ A running Proxmox VE host or cluster
✅ A user with API access (e.g., terraform-user@pve)
✅ A cloud-init template VM ready to be cloned
✅ Terraform installed on your machine
✅ Installed the Telmate Proxmox Terraform provider
🗂 Project Directory Structure
Here’s the structure of the deployment repo we’ll use:
proxmox-vm-deploy/
├── main.tf
├── provider.tf
├── variables.tf
├── terraform.tfvars
Let’s go through each file.
🧩 provider.tf – Connect Terraform to Proxmox
terraform {
required_providers {
proxmox = {
source = "Telmate/proxmox"
version = "3.0.2-rc01" # use the latest version available
}
}
}
provider "proxmox" {
pm_api_url = var.proxmox_api_url # the variable is defined in terraform.tfvars.This should match the URL in your Proxmox web interface, typically something like "https://<proxmox-ip>:8006/api2/json"
pm_parallel = 1
pm_debug = false
pm_tls_insecure = true
}
This sets up the connection to your Proxmox host. Make sure:
The user exists in Proxmox (
pveum user add terraform-user@pve)A suitable role (e.g.,
Terraform_Provisioner) with VM permissions is assignedload the API tokens for connecting to proxmox as environment variables
📜 variables.tf – Input Configuration
variable "vm_name" {
type = string
}
variable "clone" {
type = string
}
variable "ipconfig0" {
type = string
}
variable "vmid" {
type = number
}
variable "memory" {
type = number
}
variable "cores" {
type = number
}
variable "disk_size" {
type = string
description = "The size of the disk, should be at least as big as the disk in the template"
default = "20G"
}
variable "storage" {
type = string
description = "the storage where the VM disk will be created"
}
variable "ssh-public-key" {
type = string
description = "SSH public key for the VMs"
sensitive = true
}
variable "proxmox_api_url" {
type = string
description = "Proxmox API URL"
}
variable "target_node" {
type = string
description = "The Proxmox node where the VM will be created"
}
variable "nameserver" {
type = string
description = "Nameserver for the VM"
default = "1.1.1.1 8.8.8.8"
}
variable "cicustom" {
type = string
description = "Cloud-Init custom configuration"
default = "vendor=local:snippets/qemu-guest-agent.yml"
}
variable "cipassword" {
type = string
description = "Cloud-Init password for the VM"
sensitive = true
}
These variables define how your VM will look—name, clone template, IP config, memory, CPU, etc.
⚙️ terraform.tfvars – Your Custom Values
ssh-public-key = "ssh-ed25519 AAAAC3NzaC1lZXXXXXXXXXXXXXXXXXXXXXXwSOCiZ/OkpPDR3bR2tK4STIm+gnJk"
target_node = "proxmox-server-IP" # The Proxmox node where the VM will be created
cicustom = "value=local:snippets/install-packages.yml" # /var/lib/vz/snippets/install-packages.yml #
cipassword = "ubuntu"
ipconfig0 = "ip=dhcp"
vmid = 1000 # optional, if not set, Proxmox will assign a random VMID
vm_name = "ubuntu-vm"
clone = "ubuntu-24-04-cloudinit-copy" # The template to clone from
cores = 4 # Number of CPU cores
memory = 4096 # Memory in MB
nameserver = "1.1.1.1 8.8.8.8"
disk_size = "20G" # The size of the disk, should be at least as big as the disk in the template
storage = "hdd-vm-data" # The storage where the VM disk will be created
This is your configuration layer—values you want to pass to variables. Keep this file out of version control (.gitignore) if it includes sensitive info.
🏗 main.tf – Create the Virtual Machine
#create a new VM from a template with cloud-init enabled
resource "proxmox_vm_qemu" "ubuntu-vm" {
# Basic VM configuration
vmid = var.vmid
name = var.vm_name
target_node = var.target_node # The node where the VM will be created
agent = 1 # Enable the QEMU guest agent
cpu {
cores = var.cores
sockets = 1
numa = true
type = "x86-64-v2-AES"
}
memory = var.memory # Memory in MB
bios = "ovmf" # Use OVMF for UEFI support
boot = "order=scsi0" # has to be the same as the OS disk of the template
clone = var.clone # The template to clone from
scsihw = "virtio-scsi-single" # Use VirtIO SCSI controller
vm_state = "running" # "running" or "stopped"
automatic_reboot = true
# Cloud-Init configuration
cicustom = var.cicustom
ciupgrade = true # it will upgrade the OS to the latest version
nameserver = var.nameserver
ipconfig0 = var.ipconfig0
skip_ipv6 = true
ciuser = "root" # The user to use for the cloud-init script
cipassword = var.cipassword # Password for the cloud-init user
sshkeys = var.ssh-public-key # The SSH public key to be added to the VM
# Most cloud-init images require a serial device for their display
serial {
id = 0
}
# EFI disk for UEFI boot
# This is required for cloud-init images that use UEFI
# If your template does not use UEFI, you can remove this block
efidisk {
efitype = "4m"
storage = "hdd-vm-data"
}
# Disk configuration
disks {
scsi {
scsi0 {
# We have to specify the disk from our template, else Terraform will think it's not supposed to be there
disk {
storage = var.storage
# The size of the disk should be at least as big as the disk in the template. If it's smaller, the disk will be recreated
size = var.disk_size
}
}
# Some images require a cloud-init disk on the IDE controller, others on the SCSI or SATA controller
scsi1 {
cloudinit {
storage = "hdd-vm-data"
}
}
}
}
network {
id = 0
bridge = "vmbr0"
model = "virtio"
}
}
Here’s what it does:
Clones an existing cloud-init-enabled VM template
Assigns VM name, IP, memory, CPU, etc.
Injects cloud-init config, such as SSH key and default user
Enables QEMU guest agent to enhance functionality
▶️ How to Run It
Open a terminal inside the project directory and follow these steps:
# use single quotes for the API token ID because of the exclamation mark
export PM_API_TOKEN_ID='terraform-user@pve!tf_token'
export PM_API_TOKEN_SECRET="XXXXXX-XXXX-XXXXX-XXXX-XXXXXXXXXXX"
# Initialize Terraform
terraform init

# Review execution plan
terraform plan
# Apply the configuration
terraform apply

✅ Verify in Proxmox
Go to your Proxmox Web UI
You’ll see a the VM
Confirm network and SSH access
Check if the VM booted from your template and has your custom config
🛠 Troubleshooting Tips
SSH not working? Ensure cloud-init was enabled in your template and your public SSH key is valid.
Error: Permission denied? Double-check the Proxmox user's role and permissions.
Wrong IP? Validate your
ipconfig0syntax (should followip=x.x.x.x/xx,gw=x.x.x.x).
🧪 What’s Next?
Once you get one VM working, you can:
Create multiple VMs using
for_eachAutomate post-deploy scripts via
null_resourceandremote-execTurn this into a Kubernetes cluster or homelab infra stack!





