Goal

Spin up new Ubuntu VMs from a pre-configured Cloud-Init template using Terraform.

Requirements

Steps

Configuration

Define Variables (variables.tf)

variable "proxmox_api_url" {
  description = "The URL for the Proxmox API"
  type        = string
}
 
variable "vm_ssh_key" {
  description = "The public SSH key for the VM user"
  type        = string
}
 
variable "vm_ip_address" {
  description = "The static IP for the VM (CIDR format)"
  type        = string
}
 
variable "gateway" {
  description = "The gateway IP address"
  type        = string
}
 
variable "nameserver" {
  description = "The nameserver IP address"
  type        = string
}
 

Provider and Resource Definition (main.tf)

The actual “juice” (clone from an existing template and configure Cloud-Init):

terraform {
  required_providers {
    proxmox = {
      source  = "telmate/proxmox"
      version = "3.0.2-rc07"
    }
  }
}
 
provider "proxmox" {
  pm_api_url = var.proxmox_api_url
  pm_tls_insecure = true
}
 
 
resource "proxmox_vm_qemu" "test_vm_template" {
  vmid          = 100
  name          = "ubuntu-srv-01"
  target_node   = "pve-node-01"
  agent         = 1
 
  machine       = "q35"
  bios          = "ovmf"
  cpu { 
    cores       = 2
    type        = "x86-64-v2-AES"
  }
  memory        = 3072
  boot          = "order=scsi0"
  clone         = "ubuntu-noble-24-04-template"
  scsihw        = "virtio-scsi-single"
  vm_state      = "running"
  automatic_reboot = true
  tablet = false
 
  # Cloud-Init configuration
  ciupgrade  = true
  nameserver = var.nameserver
  ipconfig0  = "ip=${var.vm_ip_address},gw=${var.gateway}"
  skip_ipv6  = true
  # In a professinal env we should use a different user
  ciuser     = "root"
  sshkeys    = <<EOF
    ${var.vm_ssh_key}
    EOF
 
  efidisk {
    efitype = "4m"
    storage = "local-zfs"
    pre_enrolled_keys = true
  }
 
  disks {
    scsi {
      scsi0 {
        disk {
          storage = "local-zfs"
          size    = "35G" 
          discard = true
          iothread = true
          emulatessd = true
        }
      }
      scsi1 {
        cloudinit {
          storage = "local-zfs"
        }
      }
    }
  }
 
  network {
    id = 0
    bridge = "vmbr0"
    model  = "virtio"
  }
}
 

Assign values (terraform.tfvars)

Define the input variables

# URL of the target Proxmox node
proxmox_api_url = "https://my.proxmox.target:8006/api2/json"
# Public key to connect to the new machine
vm_ssh_key      = "ssh-rsa AAAAB3Nza..."
# replace with the desired fixed IP/CIDR
vm_ip_address   = "192.168.1.12/24"
# replace with the desired gateway
gateway         = "..."
# replace with the desired DNS server
nameserver      = "..."

Usage (provision the VM)

# Initialize
terraform init
# Plan (verify the resources to be created)
terraform plan
# Apply
terraform apply

Give the VM a minute to finish cloud-init setup, then:

ssh root@<VM_IP_ADDRESS> -i ~/.ssh/id_rsa

Optional - Destroy the VM

Simply run:

terraform destroy

And the VM (and disks) will vanish.