Terraform + Proxmox = <3


<--

I am tired of UIs

Up until now, every time i wanted to deploy a VM in my homelab i did it with the Proxmox GUI. Don’t get me wrong, the GUI is nice, but i would like to not have to repeat the same mundane task every time i want a new VM. There is an argument to be made that i probably shouldn’t run that many VMs, and that for the number of VMs i need, this isn’t worth it. However, being able to deploy a VM by just changing a few variables in a file and running terraform apply just tickles something in my nerd brain.

I also really like the repeatability of this, as i use these VM definitions to deploy K3S hosts, docker hosts and others where i want a “default” setup.

Initial requirements

To use Terraform with Proxmox we use a privoder created by Telmate. We create the initial provider.tf file as so:

terraform {
    required_providers {
      proxmox = {
        source = "telmate/proxmox"
        version = "3.0.1-rc3"
      }
    }
}

variable "proxmox_api_url" {
    type = string
}

variable "proxmox_user" {
    type = string
    sensitive = true
}

variable "proxmox_password" {
    type = string
    sensitive = true
}

variable "ssh_public_key" {
    type = string
}

provider "proxmox"{
    pm_api_url = var.proxmox_api_url
    pm_user = var.proxmox_user
    pm_password = var.proxmox_password
    pm_tls_insecure = true
    pm_otp = ""
}

I have stored all secrets to access the proxmox API in custom variables located in a .auto.tfvars file that i do not track in git. Those variables are defined in the provider such that if they are not present Terraform will complain.

In this step i had some trouble, as you can see i use a release candidate version. There seems to be a bug in version 2.9.3, and instead of trying to track it down i just switched to release candidate.

Defining the virtual machine

Now as i mentioned previously, i already have templates created in my proxmox cluster (i made these with Ansible). Therefore i can use these as a base for the provisioning of a new VM.

Here is an example definition of a VM

resource "proxmox_vm_qemu" "havneboks" {
    name = "havneboks"
    desc = "Docker master"
    target_node = "poseidon"
  
    agent = 1
    onboot = true

    clone = "VM 9001"
    cores = 4
    sockets = 1
    cpu = "host"
    memory = 3096

    # Setup the disk
    disks {
        ide {
            ide2 {
                cloudinit {
                    storage = "basseng"
                }
            }
        }
        scsi {
            scsi0 {
                disk {
                    size            = "10G"
                    storage         = "basseng"
                }
            }
        }
    }


    network {
      bridge = "vmbr0"
      model = "virtio"
    }
    scsihw = "virtio-scsi-pci"
    os_type = "cloud-init"
    ipconfig0 = "ip=192.168.1.51/24,gw=192.168.1.1"
    nameserver = "192.168.1.69"
    ciuser = "ansible"
    sshkeys = var.ssh_public_key
}

A VM is defined using the resource type of proxmox_vm_qemu with a name. I really like to use the names of the service just translated to norwegian, so in this case, this VM is called havneboks (meaning docker box).

  • name: The name of the VM (hostname)
  • desc: A description of the VM
  • target_node: Which node in the cluster should the VM be provisioned to, in this case i provision it to the node poseidon(hostname)
  • agent: Just select 1
  • onboot: Set the VM to start when the host boots
  • clone: Which template to clone the VM from
  • cores: How much horsepowa u want?
  • sockets: I only got 1 cpu in each of my boxes
  • memory: How much RAM u want?

Now, a really important part of this is the disk setup, as you have to mimic the setup of the template (sizes can be chosen freely). So in my case, i have the cloud-init disk on ide2 and the OS disk on scsi0 in the template. Therefore we create the same exact setup for this resource.

  • network: Just mimic the cloud-init
  • scsihw: Which hardware do you want the host system to use to provide scsi?
  • os_type: I use cloud-init, so we select cloud-init
  • ipconfig: Now this is quite interesting, you should set a static IP and gateway such that the VM starts with a proper IP adress (you can also use DHCP here)
  • namesever: I use a custom dns on adress .69(nice) so i set that, but if this is not set it will use “same as host”
  • ciuser: A user to be created by cloud-init for this VM
  • sshkeys: Initial SSH public keys to allow access. This is stored in a variable in my case.

Just run?

Bing bang bom. You can now deploy your VM fully automatically and about 43 seconds later access it via SSH. Now all i did was configure it with ansible, and i have a fully reproducible homelab setup.

If you want to have a look at my homelab infrastructure as code repo, you can find it at polsevev/homelab