How to Deploy Your First Proxmox Virtual Machine Using Terraform

Table of contents
- 🧠 Why Terraform for Proxmox?
- 👋 Quick heads-up!
- 📦 Prerequisites
- 🗂 Project Directory Structure
- 🧩 provider.tf – Connect Terraform to Proxmox
- 📜 variables.tf – Input Configuration
- ⚙️ terraform.tfvars – Your Custom Values
- 🏗 main.tf – Create the Virtual Machine
- ▶️ How to Run It
- ✅ Verify in Proxmox
- 🛠 Troubleshooting Tips
- 🧪 What’s Next?
🧠 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
ipconfig0
syntax (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_each
Automate post-deploy scripts via
null_resource
andremote-exec
Turn this into a Kubernetes cluster or homelab infra stack!
Subscribe to my newsletter
Read articles from Sumit Sur directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
