Terraform in Proxmox VE
Terraform provisioning of VM instances in Proxmox VE
Transitioning a homelab from manual “Click-Ops” to a fully automated workflow is a massive step forward. My goal was to create a repeatable, secure, and modular way to deploy virtual machines on Proxmox VE.
In this post, I’ll walk through the architecture, the setup process, and the technical hurdles I overcame to build a professional-grade IaC (Infrastructure as Code) pipeline.
📄 The Strategy: Packer + Terraform
The core of this setup follows a two-stage blueprint:
- The Gold Image (Packer): Instead of manually installing Ubuntu, I use Packer to build a “Golden Template.” This includes pre-installed dependencies (like QEMU agents) and
Cloud-Init configurations. - The Deployment (Terraform): Using the Telmate Proxmox provider, Terraform clones these templates to create a specific infrastructure. For example Ubuntu VMs that will be used later on as a k3s cluster or standalone VMs for testing.
🛠️ Prerequisites
Before starting, ensure you have the following:
- Proxmox VE Server: With an API Token created (e.g., terraform-prov@pve).
- Linux Environment: (WSL2 on Windows works perfectly).
- Git: For version control.
📥 Installation & Initial Setup
1. Install Terraform
1
2
3
4
sudo apt-get update && sudo apt-get install -y gnupg software-properties-common
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt-get update && sudo apt-get install terraform
2. Install SOPS & age (Secrets Management)
SOPS handles the encryption, while age provides the modern, simple key format.
1
2
3
4
5
6
# Install SOPS
sudo wget https://github.com/getsops/sops/releases/download/v3.8.1/sops-v3.8.1.linux.amd64 -O /usr/local/bin/sops
sudo chmod +x /usr/local/bin/sops
# Install age
sudo apt install age
3. Generate your Encryption Key
1
2
mkdir -p ~/.age
age-keygen -o ~/.age/age.agekey
Make sure to keep the public key (starting with
age1...) handy for the encryption step.
🔐 The Security Strategy: SOPS + age
Storing API tokens or passwords in plain-text .tfvars files is a major security risk. To solve this, I implemented an encrypted workflow:
- Create Secrets: I created a
secrets.jsoncontaining all my Proxmox credentials. - Encrypt: I used SOPS to transform that into an encrypted
secrets.enc.json. - Bridge with Locals: To keep the resource code clean, I used a
locals.tffile to map these decrypted values.1 2 3 4 5
# Example: Using locals to handle decrypted data types locals { pm_api_url = data.sops_file.my_secrets.data["PROXMOX_URL"] vm_id = tonumber(data.sops_file.my_secrets.data["VM_ID"]) # Convert string to number }
🧩 Modularity: Demo vs. Production
Instead of one giant file, I split the project: demo-resources.tf: For testing new Packer templates or create temporary testing infrastructure. k3s-resources.tf: For the live cluster, using the count meta-argument to scale masters and workers dynamically.
1
2
3
4
5
6
resource "proxmox_vm_qemu" "k3s_master" {
count = 3
name = "k3s-master-0${count.index + 1}"
vmid = local.vm_id + count.index
# ... specs ...
}
🚧 Challenges & Lessons Learned
Data Type Friction
The SOPS provider treats all decrypted values as strings. Since Proxmox expects integers for vmid or memory, I had to explicitly use the tonumber() function in Terraform.
Provider Discovery
Terraform by default looks for an official Hashicorp SOPS provider. Since it’s a community provider, I had to explicitly define the source as carlpett/sops in the required_providers block.
🏆 Final Result
The result is a workflow where I can update an Ubuntu template in Packer and roll out an entirely new infrastructure using terraform.
1
2
# to check what resources will be created
terraform plan
1
2
# to create/update those resources
terraform apply
Repository
You can find the full source code in my GitHub repository:
👉 Link to your GitHub Repo Here
Stay tuned for more! 😎
