This guide demonstrates how to use Terraform to automate infrastructure provisioning on Proxmox Virtual Environment (VE). Proxmox is an open-source server virtualization platform that combines KVM hypervisor and LXC containers with an intuitive web interface and REST API.
Overview
Terraform enables Infrastructure as Code (IaC) for Proxmox, allowing you to:
- Automate VM provisioning - Create and manage virtual machines programmatically
- Version control infrastructure - Track changes to your infrastructure in Git
- Ensure consistency - Deploy identical environments across development, staging, and production
- Scale efficiently - Provision multiple VMs with minimal configuration
- Integrate cloud-init - Automate guest OS configuration and initialization
Prerequisites
Proxmox VE Requirements
- Proxmox VE 7.x or later installed and configured
- API access enabled
- VM template with cloud-init support (recommended)
- Network bridge configured (typically vmbr0)
- Sufficient storage for VM disks
Local Development Requirements
- Terraform >= 1.0 installed
- Network connectivity to Proxmox host
- API credentials (token authentication recommended)
Install Terraform
# Download and install Terraform (Linux)
wget https://releases.hashicorp.com/terraform/1.6.0/terraform_1.6.0_linux_amd64.zip
unzip terraform_1.6.0_linux_amd64.zip
sudo mv terraform /usr/local/bin/
terraform --version
Proxmox API Setup
Create API User and Token
Create a dedicated user and API token for Terraform:
# Create Terraform provisioning user
pveum user add terraform-prov@pve --comment "Terraform Provisioning User"
# Create API token
pveum user token add terraform-prov@pve terraform_id --privsep=0
# Output will include:
# ┌──────────────┬──────────────────────────────────────┐
# │ key │ value │
# ╞══════════════╪══════════════════════════════════════╡
# │ full-tokenid │ terraform-prov@pve!terraform_id │
# ├──────────────┼──────────────────────────────────────┤
# │ info │ {"privsep":"0"} │
# ├──────────────┼──────────────────────────────────────┤
# │ value │ xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx │
# └──────────────┴──────────────────────────────────────┘
# Save the token value - it will only be displayed once!
Grant Permissions
Grant necessary permissions to the Terraform user:
# Create custom role with minimal required permissions (REQUIRED for production)
pveum role add TerraformProvisioner -privs "VM.Allocate VM.Clone VM.Config.CDROM VM.Config.CPU VM.Config.Cloudinit VM.Config.Disk VM.Config.HWType VM.Config.Memory VM.Config.Network VM.Config.Options VM.Monitor VM.Audit VM.PowerMgmt Datastore.AllocateSpace Datastore.Audit Pool.Allocate Sys.Audit Sys.Console Sys.Modify SDN.Use"
# Assign role to user at root level
pveum aclmod / -user terraform-prov@pve -role TerraformProvisioner
Security Warning: Never use Administrator or PVEAdmin roles for Terraform automation. Always follow least-privilege principles by creating custom roles with only the permissions required for your specific use case.
Terraform Provider Configuration
Provider Setup
Create provider.tf:
terraform {
required_version = ">= 1.0"
required_providers {
proxmox = {
source = "bpg/proxmox"
version = "0.46.5" # Pin exact version for production
}
}
}
provider "proxmox" {
endpoint = var.proxmox_api_url
# Token format: username@realm!tokenid=secret
api_token = "${var.proxmox_api_token_id}=${var.proxmox_api_token_secret}"
# Set to false in production with valid SSL certificate
insecure = var.insecure_skip_tls_verify
# Optional: SSH access for certain operations
ssh {
agent = true
username = "root"
}
}
Variables Configuration
Create variables.tf:
variable "proxmox_api_url" {
type = string
description = "Proxmox API URL (e.g., https://proxmox.example.com:8006/api2/json)"
validation {
condition = can(regex("^https?://", var.proxmox_api_url))
error_message = "API URL must start with http:// or https://"
}
}
variable "proxmox_api_token_id" {
type = string
description = "Proxmox API token ID (e.g., terraform-prov@pve!terraform_id)"
validation {
condition = can(regex("^[^@]+@[^!]+![^=]+$", var.proxmox_api_token_id))
error_message = "Token ID must be in format: username@realm!tokenid"
}
}
variable "proxmox_api_token_secret" {
type = string
description = "Proxmox API token secret"
sensitive = true
validation {
condition = length(var.proxmox_api_token_secret) >= 16
error_message = "Token secret must be at least 16 characters"
}
}
variable "insecure_skip_tls_verify" {
type = bool
description = "Skip TLS certificate verification (only for development)"
default = false
}
variable "target_node" {
type = string
description = "Proxmox node name"
default = "pve"
validation {
condition = length(var.target_node) > 0
error_message = "Target node name cannot be empty"
}
}
variable "datastore_id" {
type = string
description = "Proxmox datastore ID for VM disks"
default = "local-lvm"
}
variable "template_name" {
type = string
description = "Name of the VM template to clone"
default = "ubuntu-cloud-template"
}
variable "template_id" {
type = number
description = "Template VM ID to clone from"
default = 9000
validation {
condition = var.template_id >= 100 && var.template_id <= 999999999
error_message = "Template ID must be between 100 and 999999999"
}
}
variable "vm_count" {
type = number
description = "Number of VMs to create"
default = 1
validation {
condition = var.vm_count >= 1 && var.vm_count <= 100
error_message = "VM count must be between 1 and 100"
}
}
variable "vm_name_prefix" {
type = string
description = "Prefix for VM names"
default = "terraform-vm"
validation {
condition = can(regex("^[a-z0-9][a-z0-9-]*$", var.vm_name_prefix))
error_message = "VM name prefix must start with alphanumeric and contain only lowercase letters, numbers, and hyphens"
}
}
variable "vm_memory" {
type = number
description = "VM memory in MB"
default = 2048
validation {
condition = var.vm_memory >= 512 && var.vm_memory <= 524288
error_message = "Memory must be between 512MB and 512GB"
}
}
variable "vm_cores" {
type = number
description = "Number of CPU cores"
default = 2
validation {
condition = var.vm_cores >= 1 && var.vm_cores <= 128
error_message = "CPU cores must be between 1 and 128"
}
}
variable "vm_disk_size" {
type = string
description = "VM disk size (e.g., 20G)"
default = "20G"
validation {
condition = can(regex("^[0-9]+[KMGT]$", var.vm_disk_size))
error_message = "Disk size must be in format: number followed by K, M, G, or T (e.g., 20G, 500M)"
}
}
variable "network_bridge" {
type = string
description = "Network bridge name"
default = "vmbr0"
validation {
condition = can(regex("^vmbr[0-9]+$", var.network_bridge))
error_message = "Network bridge must be in format: vmbr followed by number (e.g., vmbr0)"
}
}
variable "ssh_public_key" {
type = string
description = "SSH public key for cloud-init"
validation {
condition = can(regex("^(ssh-rsa|ssh-ed25519|ecdsa-sha2-nistp256) ", var.ssh_public_key))
error_message = "Must be a valid SSH public key (ssh-rsa, ssh-ed25519, or ecdsa-sha2-nistp256)"
}
}
variable "environment" {
type = string
description = "Environment name (dev, staging, production)"
default = "dev"
validation {
condition = contains(["dev", "staging", "production"], var.environment)
error_message = "Environment must be dev, staging, or production"
}
}
Create terraform.tfvars (never commit this file):
proxmox_api_url = "https://192.168.1.100:8006/api2/json"
proxmox_api_token_id = "terraform-prov@pve!terraform_id"
proxmox_api_token_secret = "your-secret-token-here"
target_node = "pve"
template_name = "ubuntu-cloud-template"
vm_count = 3
vm_name_prefix = "web-server"
vm_memory = 4096
vm_cores = 2
vm_disk_size = "30G"
ssh_public_key = "ssh-rsa AAAAB3NzaC1yc2E... user@host"
Creating VM Templates
Ubuntu Cloud Image Template
Create a cloud-init enabled template:
# Download Ubuntu cloud image
wget https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img
# Create VM
qm create 9000 --name ubuntu-cloud-template --memory 2048 --cores 2 --net0 virtio,bridge=vmbr0
# Import disk
qm importdisk 9000 jammy-server-cloudimg-amd64.img local-lvm
# Configure VM
qm set 9000 --scsihw virtio-scsi-pci --scsi0 local-lvm:vm-9000-disk-0
qm set 9000 --boot c --bootdisk scsi0
qm set 9000 --ide2 local-lvm:cloudinit
qm set 9000 --serial0 socket --vga serial0
qm set 9000 --agent enabled=1
# Convert to template
qm template 9000
# Verify template exists
qm list
Debian Cloud Image Template
# Download Debian cloud image
wget https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2
# Create and configure VM
qm create 9001 --name debian-cloud-template --memory 2048 --cores 2 --net0 virtio,bridge=vmbr0
qm importdisk 9001 debian-12-generic-amd64.qcow2 local-lvm
qm set 9001 --scsihw virtio-scsi-pci --scsi0 local-lvm:vm-9001-disk-0
qm set 9001 --boot c --bootdisk scsi0
qm set 9001 --ide2 local-lvm:cloudinit
qm set 9001 --serial0 socket --vga serial0
qm set 9001 --agent enabled=1
qm template 9001
Basic VM Provisioning
Single VM Example
Create main.tf:
resource "proxmox_virtual_environment_vm" "vm" {
name = var.vm_name_prefix
node_name = var.target_node
vm_id = 100
clone {
vm_id = var.template_id
full = true
}
agent {
enabled = true
timeout = "60s" # Wait for agent to be ready
}
cpu {
cores = var.vm_cores
type = "host"
}
memory {
dedicated = var.vm_memory
}
disk {
datastore_id = var.datastore_id
interface = "scsi0"
size = var.vm_disk_size
iothread = true
cache = "writethrough"
}
network_device {
bridge = var.network_bridge
model = "virtio"
}
initialization {
ip_config {
ipv4 {
address = "dhcp"
}
}
user_account {
username = "ubuntu"
keys = [var.ssh_public_key]
}
}
operating_system {
type = "l26" # Linux 2.6+ kernel
}
on_boot = true
lifecycle {
ignore_changes = [
initialization, # Cloud-init changes don't trigger recreation
]
}
tags = [var.environment, "terraform-managed"]
}
Multiple VMs with Count
resource "proxmox_virtual_environment_vm" "vms" {
count = var.vm_count
name = "${var.vm_name_prefix}-${format("%02d", count.index + 1)}"
node_name = var.target_node
vm_id = 100 + count.index
clone {
vm_id = var.template_id
full = true
}
agent {
enabled = true
timeout = "60s"
}
cpu {
cores = var.vm_cores
type = "host"
}
memory {
dedicated = var.vm_memory
}
disk {
datastore_id = var.datastore_id
interface = "scsi0"
size = var.vm_disk_size
iothread = true
cache = "writethrough"
}
network_device {
bridge = var.network_bridge
model = "virtio"
}
initialization {
ip_config {
ipv4 {
address = "dhcp"
}
}
user_account {
username = "ubuntu"
keys = [var.ssh_public_key]
}
}
operating_system {
type = "l26"
}
on_boot = true
lifecycle {
create_before_destroy = true
ignore_changes = [initialization]
prevent_destroy = var.environment == "production"
}
tags = [var.environment, "terraform-managed", "vm-${count.index + 1}"]
# Wait for VM to be ready before continuing
timeouts {
create = "10m"
update = "10m"
delete = "5m"
}
}
Advanced VM Configuration
Static IP Configuration
variable "vm_ips" {
type = map(object({
ip = string
gateway = string
vm_id = number
}))
description = "Map of VM names to their network configuration"
default = {
"vm-1" = {
ip = "192.168.1.101/24"
gateway = "192.168.1.1"
vm_id = 101
}
"vm-2" = {
ip = "192.168.1.102/24"
gateway = "192.168.1.1"
vm_id = 102
}
"vm-3" = {
ip = "192.168.1.103/24"
gateway = "192.168.1.1"
vm_id = 103
}
}
validation {
condition = alltrue([
for k, v in var.vm_ips : can(regex("^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}/[0-9]{1,2}$", v.ip))
])
error_message = "All IP addresses must be in CIDR format (e.g., 192.168.1.101/24)"
}
}
resource "proxmox_virtual_environment_vm" "static_ip_vms" {
for_each = var.vm_ips
name = each.key
node_name = var.target_node
vm_id = each.value.vm_id
clone {
vm_id = var.template_id
full = true
}
agent {
enabled = true
timeout = "60s"
}
cpu {
cores = var.vm_cores
type = "host"
}
memory {
dedicated = var.vm_memory
}
disk {
datastore_id = var.datastore_id
interface = "scsi0"
size = var.vm_disk_size
iothread = true
cache = "writethrough"
}
network_device {
bridge = var.network_bridge
model = "virtio"
}
initialization {
ip_config {
ipv4 {
address = each.value.ip
gateway = each.value.gateway
}
}
dns {
servers = ["8.8.8.8", "8.8.4.4"]
}
user_account {
username = "ubuntu"
keys = [var.ssh_public_key]
}
}
operating_system {
type = "l26"
}
on_boot = true
lifecycle {
create_before_destroy = true
ignore_changes = [initialization]
prevent_destroy = var.environment == "production"
}
tags = [var.environment, "terraform-managed", "static-ip"]
}
Cloud-Init User Data
resource "proxmox_virtual_environment_vm" "cloudinit_vm" {
name = "web-server"
node_name = var.target_node
vm_id = 200
clone {
vm_id = 9000
full = true
}
agent {
enabled = true
}
cpu {
cores = 4
type = "host"
}
memory {
dedicated = 8192
}
disk {
datastore_id = "local-lvm"
interface = "scsi0"
size = "50G"
}
network_device {
bridge = "vmbr0"
model = "virtio"
}
initialization {
ip_config {
ipv4 {
address = "192.168.1.150/24"
gateway = "192.168.1.1"
}
}
dns {
servers = ["8.8.8.8", "1.1.1.1"]
domain = "example.com"
}
user_account {
username = "admin"
keys = [var.ssh_public_key]
# SECURITY: Never use passwords - SSH keys only!
# To set password: use cloud-init user_data with hashed password
}
user_data_file_id = proxmox_virtual_environment_file.cloud_init_config.id
}
operating_system {
type = "l26"
}
on_boot = true
}
resource "proxmox_virtual_environment_file" "cloud_init_config" {
content_type = "snippets"
datastore_id = "local"
node_name = var.target_node
source_raw {
data = templatefile("${path.module}/cloud-init-config.tftpl", {
hostname = "web-server"
environment = var.environment
ssh_keys = [var.ssh_public_key]
})
file_name = "cloud-init-${var.environment}-config.yaml"
}
}
# Create cloud-init-config.tftpl file with:
locals {
cloud_init_template = <<-EOF
#cloud-config
hostname: ${hostname}
manage_etc_hosts: true
# Security hardening
ssh_pwauth: false
disable_root: true
packages:
- qemu-guest-agent
- nginx
- curl
- htop
- vim
- fail2ban
- ufw
package_update: true
package_upgrade: true
runcmd:
# Start QEMU guest agent
- systemctl enable qemu-guest-agent
- systemctl start qemu-guest-agent
# Configure firewall
- ufw allow 22/tcp
- ufw allow 80/tcp
- ufw allow 443/tcp
- ufw --force enable
# Configure nginx
- systemctl enable nginx
- systemctl start nginx
- echo "Welcome to Terraform-provisioned VM (${environment})" > /var/www/html/index.html
users:
- name: admin
groups: sudo
shell: /bin/bash
sudo: ['ALL=(ALL) NOPASSWD:ALL']
ssh_authorized_keys: %{ for key in ssh_keys }
- ${key}
%{ endfor }
timezone: UTC
write_files:
- path: /etc/motd
content: |
****************************************************
* This VM is managed by Terraform *
* Environment: ${environment} *
* Do not make manual changes without documenting *
****************************************************
EOF
}
Storage Configuration
Multiple Disks
resource "proxmox_virtual_environment_vm" "multi_disk_vm" {
name = "database-server"
node_name = var.target_node
vm_id = 300
clone {
vm_id = 9000
full = true
}
agent {
enabled = true
}
cpu {
cores = 8
type = "host"
}
memory {
dedicated = 16384
}
# OS disk
disk {
datastore_id = var.datastore_id
interface = "scsi0"
size = "50G"
cache = "writethrough"
iothread = true
}
# Data disk (consider using separate datastore for better performance)
disk {
datastore_id = var.datastore_id
interface = "scsi1"
size = "200G"
cache = "writethrough"
iothread = true
discard = "on" # Enable TRIM for SSD
}
# Backup disk
disk {
datastore_id = var.datastore_id
interface = "scsi2"
size = "100G"
cache = "writethrough"
iothread = true
}
network_device {
bridge = "vmbr0"
model = "virtio"
}
initialization {
ip_config {
ipv4 {
address = "192.168.1.200/24"
gateway = "192.168.1.1"
}
}
user_account {
username = "dbadmin"
keys = [var.ssh_public_key]
}
}
operating_system {
type = "l26"
}
on_boot = true
}
Networking Configuration
Multiple Network Interfaces
resource "proxmox_virtual_environment_vm" "multi_nic_vm" {
name = "firewall-vm"
node_name = var.target_node
vm_id = 400
clone {
vm_id = 9000
full = true
}
agent {
enabled = true
}
cpu {
cores = 4
type = "host"
}
memory {
dedicated = 4096
}
disk {
datastore_id = "local-lvm"
interface = "scsi0"
size = "30G"
}
# WAN interface
network_device {
bridge = "vmbr0"
model = "virtio"
}
# LAN interface
network_device {
bridge = "vmbr1"
model = "virtio"
}
# DMZ interface
network_device {
bridge = "vmbr2"
model = "virtio"
}
initialization {
ip_config {
ipv4 {
address = "192.168.1.254/24"
gateway = "192.168.1.1"
}
}
ip_config {
ipv4 {
address = "10.0.0.1/24"
}
}
ip_config {
ipv4 {
address = "172.16.0.1/24"
}
}
user_account {
username = "admin"
keys = [var.ssh_public_key]
}
}
operating_system {
type = "l26"
}
on_boot = true
}
Outputs
Create outputs.tf:
output "vm_ids" {
description = "IDs of created VMs"
value = [for vm in proxmox_virtual_environment_vm.vms : vm.vm_id]
}
output "vm_names" {
description = "Names of created VMs"
value = [for vm in proxmox_virtual_environment_vm.vms : vm.name]
}
output "vm_details" {
description = "Detailed information about created VMs"
value = {
for vm in proxmox_virtual_environment_vm.vms :
vm.name => {
vm_id = vm.vm_id
node = vm.node_name
# DHCP may take time to assign IP - check after cloud-init completes
ipv4_addresses = length(vm.ipv4_addresses) > 1 ? vm.ipv4_addresses[1] : ["IP not yet assigned"]
mac_addresses = vm.mac_addresses
status = vm.started ? "running" : "stopped"
}
}
}
output "static_ip_vms" {
description = "Static IP VM information"
value = {
for vm in proxmox_virtual_environment_vm.static_ip_vms :
vm.name => {
vm_id = vm.vm_id
ip_address = split("/", var.vm_ips[vm.name].ip)[0]
ssh_command = "ssh ubuntu@${split("/", var.vm_ips[vm.name].ip)[0]}"
}
}
sensitive = false
}
output "connection_info" {
description = "Quick reference for connecting to VMs"
value = <<-EOT
To connect to VMs:
Static IP VMs:
%{ for name, config in var.vm_ips ~}
- ${name}: ssh ubuntu@${split("/", config.ip)[0]}
%{ endfor ~}
DHCP VMs: Run 'terraform output vm_details' to see assigned IPs
Note: VMs may take 1-2 minutes after creation for cloud-init to complete.
EOT
}
State Management
Remote State with S3
Create backend.tf:
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "proxmox/terraform.tfstate"
region = "us-east-1"
encrypt = true
kms_key_id = "arn:aws:kms:us-east-1:ACCOUNT:key/KEY-ID" # Optional: KMS encryption
dynamodb_table = "terraform-state-lock"
# Enable workspace support
workspace_key_prefix = "workspaces"
}
}
Remote State with Consul
terraform {
backend "consul" {
address = "consul.example.com:8500"
scheme = "https"
path = "terraform/proxmox"
}
}
Deployment Workflow
Initialize and Deploy
# Initialize Terraform
terraform init
# Validate configuration
terraform validate
# Format configuration files
terraform fmt
# Plan infrastructure changes
terraform plan -out=tfplan
# Review plan output carefully
# Apply changes
terraform apply tfplan
# Verify deployment
terraform show
# Test SSH connectivity
ssh ubuntu@<vm-ip-address>
Update Infrastructure
# Modify variables or configuration
# Plan changes
terraform plan -out=tfplan
# Review changes
# Apply updates
terraform apply tfplan
Destroy Infrastructure
# Preview destruction
terraform plan -destroy
# Destroy all resources
terraform destroy
# Or destroy specific resource
terraform destroy -target=proxmox_virtual_environment_vm.vms[0]
Module Organization
Reusable VM Module
Create modules/proxmox-vm/main.tf:
variable "node_name" {
type = string
}
variable "vm_name" {
type = string
}
variable "vm_id" {
type = number
}
variable "template_id" {
type = number
default = 9000
}
variable "cores" {
type = number
default = 2
}
variable "memory" {
type = number
default = 2048
}
variable "disk_size" {
type = string
default = "20G"
}
variable "ip_address" {
type = string
}
variable "gateway" {
type = string
}
variable "ssh_public_key" {
type = string
}
variable "datastore_id" {
type = string
description = "Datastore ID for VM disks"
default = "local-lvm"
}
variable "network_bridge" {
type = string
description = "Network bridge"
default = "vmbr0"
}
variable "environment" {
type = string
description = "Environment tag"
default = "dev"
}
resource "proxmox_virtual_environment_vm" "vm" {
name = var.vm_name
node_name = var.node_name
vm_id = var.vm_id
clone {
vm_id = var.template_id
full = true
}
agent {
enabled = true
timeout = "60s"
}
cpu {
cores = var.cores
type = "host"
}
memory {
dedicated = var.memory
}
disk {
datastore_id = var.datastore_id
interface = "scsi0"
size = var.disk_size
iothread = true
cache = "writethrough"
}
network_device {
bridge = var.network_bridge
model = "virtio"
}
initialization {
ip_config {
ipv4 {
address = var.ip_address
gateway = var.gateway
}
}
user_account {
username = "ubuntu"
keys = [var.ssh_public_key]
}
}
operating_system {
type = "l26"
}
on_boot = true
lifecycle {
create_before_destroy = true
ignore_changes = [initialization]
prevent_destroy = var.environment == "production"
}
tags = [var.environment, "terraform-managed", "module-created"]
}
output "vm_id" {
description = "VM ID"
value = proxmox_virtual_environment_vm.vm.vm_id
}
output "vm_name" {
description = "VM name"
value = proxmox_virtual_environment_vm.vm.name
}
output "ip_address" {
description = "Configured IP address"
value = var.ip_address
}
output "actual_ipv4_addresses" {
description = "Actual IPv4 addresses assigned to VM"
value = length(proxmox_virtual_environment_vm.vm.ipv4_addresses) > 1 ? proxmox_virtual_environment_vm.vm.ipv4_addresses[1] : []
}
Create modules/proxmox-vm/variables.tf and modules/proxmox-vm/outputs.tf as needed.
Using the Module
In your root main.tf:
module "web_servers" {
source = "./modules/proxmox-vm"
for_each = {
"web-01" = { ip = "192.168.1.101/24", vm_id = 101 }
"web-02" = { ip = "192.168.1.102/24", vm_id = 102 }
"web-03" = { ip = "192.168.1.103/24", vm_id = 103 }
}
node_name = var.target_node
vm_name = each.key
vm_id = each.value.vm_id
cores = 4
memory = 8192
disk_size = "50G"
ip_address = each.value.ip
gateway = "192.168.1.1"
ssh_public_key = var.ssh_public_key
}
output "web_servers" {
value = {
for k, v in module.web_servers :
k => {
vm_id = v.vm_id
ip = v.ip_address
}
}
}
Best Practices
Security
- Use API tokens instead of password authentication
- Apply least privilege - create custom roles with minimal permissions
- Never commit secrets - use environment variables or secret management tools
- Enable SSL/TLS - use valid certificates in production environments
- Rotate credentials regularly
- Use SSH keys instead of passwords for VM access
Infrastructure Management
- Version control - store Terraform code in Git
- Use remote state - S3, Consul, or Terraform Cloud for state storage
- Enable state locking - prevent concurrent modifications
- Use workspaces - separate dev, staging, and production environments
- Implement CI/CD - automate terraform plan/apply in pipelines
- Tag resources - use consistent naming and tagging conventions
Performance Optimization
- Use cloud-init for faster provisioning
- Enable QEMU agent for better VM management
- Use virtio drivers for optimal disk and network performance
- Set appropriate I/O threads for disk-intensive workloads
- Configure CPU type - "host" for best performance
- Use SSD storage pools for database and high-IOPS workloads
Maintenance
- Regular backups - use Proxmox backup solutions
- Monitor resources - track CPU, memory, disk usage
- Update templates - keep base images current with security patches
- Test disaster recovery - validate restore procedures
- Document infrastructure - maintain README and runbooks
- Review logs - check Terraform and Proxmox logs regularly
Troubleshooting
Common Issues
Issue: Authentication failures
Error: authentication failed
Solution:
- Verify API token is correct
- Check token has not expired
- Ensure user has proper permissions
- Test API access with curl
curl -k -H "Authorization: PVEAPIToken=terraform-prov@pve!terraform_id=your-token" \
https://proxmox:8006/api2/json/version
Issue: Template not found
Error: VM template 9000 not found
Solution:
- Verify template exists:
qm list - Check template ID in configuration
- Ensure template is on correct node
- Verify storage is accessible
Issue: IP address conflicts
Error: IP address already in use
Solution:
- Check for duplicate IP assignments
- Use DHCP for dynamic environments
- Maintain IP address inventory
- Use Terraform data sources to query existing VMs
Issue: Disk space errors
Error: not enough space on datastore
Solution:
- Check storage capacity:
pvesm status - Clean up old VM disks
- Use different storage pool
- Reduce disk size allocation
Issue: Slow provisioning
Solution:
- Use cloud-init for faster configuration
- Optimize template images (remove unnecessary packages)
- Use local storage instead of NFS for better performance
- Enable thin provisioning
- Use SSD storage pools
Debug Mode
Enable detailed logging:
# Set Terraform debug mode
export TF_LOG=DEBUG
export TF_LOG_PATH=terraform-debug.log
terraform apply
# Review debug log
less terraform-debug.log
Advanced Topics
Import Existing VMs
# Import existing VM into Terraform state
terraform import proxmox_virtual_environment_vm.imported_vm <node>/<vmid>
# Example
terraform import proxmox_virtual_environment_vm.imported_vm pve/105
Using Data Sources
# Query existing template
data "proxmox_virtual_environment_vms" "templates" {
node_name = var.target_node
filter {
name = "template"
values = [true]
}
}
# Use template in resource
resource "proxmox_virtual_environment_vm" "vm_from_data" {
name = "data-source-vm"
node_name = var.target_node
clone {
vm_id = data.proxmox_virtual_environment_vms.templates.vms[0].vm_id
full = true
}
# ... rest of configuration
}
Provisioners for Post-Deployment
resource "proxmox_virtual_environment_vm" "provisioned_vm" {
name = "app-server"
node_name = var.target_node
# ... configuration ...
provisioner "remote-exec" {
inline = [
"sudo apt-get update",
"sudo apt-get install -y docker.io",
"sudo systemctl enable docker",
"sudo systemctl start docker"
]
connection {
type = "ssh"
user = "ubuntu"
private_key = file("~/.ssh/id_rsa")
host = self.ipv4_addresses[1][0]
}
}
}
Additional Resources
- Proxmox VE Documentation
- Terraform Proxmox Provider (bpg)
- Cloud-Init Documentation
- Terraform Best Practices
Important Notes:
- Always test in a non-production environment first
- Maintain backups before making infrastructure changes
- Document custom configurations and network topology
- Use version control for all Terraform code
- Never commit sensitive credentials to version control