Skip to content

-proxmox - vm's - vm - virtualization - terraform

Terraform with Proxmox


Cloud Init Intro

The complete terraform api reference for proxmox, find every variable you can set for the proxmox_vm_qemu provider:


  1. Proxmox
  2. Cloud-Init Optional but EXTREMELY RECOMMENDED
  3. A VM, I'm using a cloud-init Alpine VM for this test.

Set up Proxmox User and API Key

There are many articles on this step already, is a good one.

Set up C&C

In the VM you need to install terraform.


apk add terraform --repository=

Create some files

You need at bare minimum from my (minimal, cursory) understanding, and files.

  1. : This file contains the main definition of your vm's
  2. : Specify the variables you want to be more easily set without having to dig through the entirety of to do so.


#Set your public SSH key here
variable "ssh_key" {
  default = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDBi7A81T7smfUrtyqDjg8kRjiuNu6KmS/CGVBMOn0WAPg/k5D4uAZT3CsO/MrpwFyx5Zx+wFd82Y+e68WRzqV2gsNszCUiG+7BEWD+ArDMUf/zbj7vafR4xzm8f9bPVRmV9PPqjnauZadAcwEP7rGHa8n8Eun8khB/cyfkRU3K/ziE7vhVCku82ECsYr5vsHs9+M+Q6j/IXoKFD9blBdqVgwUR6NjvKmpIo2kqe2f64mKrE0x2F95KWsWKjVu0ugwjYrpwmLmQFJYr4xBa+XAlwL9K99rJQrcKWUskiupbtYs0OgQPEnYamqQjLgB0qe4DD9bB4N/6NZMioVA24oXx deadc0de@deadc0de-PC"
variable "hostname" {
  default = "terraform-test"
#Establish which Proxmox host you'd like to spin a VM up on
variable "proxmox_host" {
    default = "hpve"
#Specify which template name you'd like to use
variable "template_name" {
    default = "10GB-Alpine-Cloud"
#Establish which nic you would like to utilize
variable "nic_name" {
    default = "vmbr0"
#Establish the VLAN you'd like to use
variable "vlan_num" {
    default = "1"
#Provide the url of the host you would like the API to communicate on.
#It is safe to default to setting this as the URL for what you used
#as your `proxmox_host`, although they can be different
variable "api_url" {
    default = ""
#Blank var for use by terraform.tfvars
variable "token_secret" {
    default = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
#Blank var for use by terraform.tfvars
variable "token_id" {
    default = "EXAMPLE@pve!terraform_api_token"

terraform {
  required_providers {
    proxmox = {
      source = "telmate/proxmox"
      #latest version as of Nov 30 2022
      version = "2.9.11"

provider "proxmox" {
  # References our file to plug in the api_url
  pm_api_url = var.api_url
  # References our secrets.tfvars file to plug in our token_id
  pm_api_token_id = var.token_id
  # References our secrets.tfvars to plug in our token_secret
  pm_api_token_secret = var.token_secret
  # Default to `true` unless you have TLS working within your pve setup
  pm_tls_insecure = true

#data "template_file" "user_data" {
#  template = file("cloud-init.yml")

resource "proxmox_vm_qemu" "proxmox_vm" {
  count             = 1
  name              = "${var.hostname}-${count.index}"
  target_node       = var.proxmox_host
  clone             = var.template_name
  os_type           = "cloud-init"
  cores             = 1
  sockets           = "1"
  cpu               = "host"
  full_clone        = false
  agent             = 1
  memory            = 2048
  scsihw            = "virtio-scsi-pci"
  bootdisk          = "virtio0"
#  cicustom          = data.template_file.user_data.rendered
  disk {
    slot              = "0"
    size            = "10G"
    type            = "virtio"
    storage         = "local-zfs"
    iothread        = 1
  network {
  #    id              = "0"
    model           = "virtio"
    bridge          = "vmbr0"
  lifecycle {
    ignore_changes  = [

  cicustom = "user=cloud-inits:snippets/gitlab-runner-user.yml,network=cloud-inits:snippets/gitlab-runner-network.yml,vendor=cloud-inits:snippets/gitlab-runner-install.yml"


WIP is a WIP. Currently it uses cloud-init snippets stored in a directory volume named 'cloud-inits' on the proxmox hosts. Future versions will login via ssh to the proxmox server and upload these files using terraforms built in providers to do exactly that. An example (from:

(This would be modified to the desired configuration and added to, probably near the top, for each of the cloud-init files that need to be uploaded.)

# Source the Cloud Init Config file
data "template_file" "cloud_init_deb10_vm-01" {
  template  = "${file("${path.module}/files/cloud_init_deb10.cloud_config")}"

  vars = {
    ssh_key = file("~/.ssh/")
    hostname = "vm-01"
    domain = "yetiops.lab"

# Create a local copy of the file, to transfer to Proxmox
resource "local_file" "cloud_init_deb10_vm-01" {
  content   = data.template_file.cloud_init_deb10_vm-01.rendered
  filename  = "${path.module}/files/user_data_cloud_init_deb10_vm-01.cfg"

# Transfer the file to the Proxmox Host
resource "null_resource" "cloud_init_deb10_vm-01" {
  connection {
    type    = "ssh"
    user    = "root"
    private_key = file("~/.ssh/id_rsa")
    host    = ""

  provisioner "file" {
    source       = local_file.cloud_init_deb10_vm-01.filename
    destination  = "/var/lib/vz/snippets/cloud_init_deb10_vm-01.yml"

Running terraform


Now with and in place, and terraform installed, terraform can be initialized.

terraform init


Run terraform in plan mode to visualize the changes to be made

terraform plan -out plan


Once the plan is approved you can apply it.

terraform apply plan

If you need to cancel, control+c twice can leave dirty VM's, let things play out. I tried it to see what would happen while it was creating a vm, and it did still make the vm, but then terraform was unable to destroy it and I had to manually do that. Just be careful here.


If you're done with your VM and ready to destroy it

terraform destroy 

Further Reading

Next Chapter