---
title: Terraform + Proxmox = <3
description: Using Terraform to deploy Virtual Machines
pubDate: 09.29.2024
---

## 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](https://github.com/Telmate/terraform-provider-proxmox). We create the initial `provider.tf` file as so:
```json
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

```terraform
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](https://github.com/polsevev/homelab)

![](./images/i-have-homelab.webp)