- HCL 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .gitignore | ||
| .terraform.lock.hcl | ||
| CLAUDE.md | ||
| locals.tf | ||
| main.tf | ||
| outputs.tf | ||
| providers.tf | ||
| README.md | ||
| terraform.tfvars.example | ||
| variables.tf | ||
| versions.tf | ||
proxmox-terraform
Declarative, reproducible management of Proxmox VE virtual machines using OpenTofu and the bpg/proxmox provider.
Requirements
- OpenTofu
>= 1.8.0 - Proxmox provider
bpg/proxmox ~> 0.111.0(pinned inversions.tf) - A Proxmox VE cluster/node reachable over the API
- An API token with permissions to manage VMs, storage, and download files on the target node
Install OpenTofu: https://opentofu.org/docs/intro/install/
Project layout
.
├── versions.tf # OpenTofu + provider version pins
├── providers.tf # Proxmox provider configuration
├── variables.tf # Input variables, including the `vms` map
├── locals.tf # Derived values
├── main.tf # Cloud image downloads + VM resources
├── outputs.tf # VM IDs, names, nodes, IPs
├── terraform.tfvars.example # Example variable values (no secrets)
└── .gitignore
VMs are defined data-driven via the vms map variable and created with for_each, so adding a VM does not require copying resource blocks. A modules/vm module is not present; introduce one only if VM patterns diverge enough to need a reusable interface.
Authentication
Credentials are never hard-coded. Provide them via environment variables (preferred) or terraform.tfvars (never committed):
export PROXMOX_VE_ENDPOINT="https://proxmox.example.com:8006"
export PROXMOX_VE_API_TOKEN="user@realm!tokenid=uuid"
If you set proxmox_endpoint / proxmox_api_token in terraform.tfvars instead, keep that file local only — it is excluded via .gitignore.
Usage
# Copy and edit the example variables
cp terraform.tfvars.example terraform.tfvars
# Format, initialize, and validate
tofu fmt -recursive
tofu init
tofu validate
# Review changes before applying
tofu plan
# Apply only after reviewing the plan
tofu apply
Adding a VM
Add an entry to the vms map (in terraform.tfvars or a *.auto.tfvars file), then plan and apply:
vms = {
web01 = {
name = "web01"
cores = 2
memory_mb = 4096
disk_gb = 32
image_url = "https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img"
ip_config = "10.0.0.20/24"
gateway = "10.0.0.1"
tags = ["web"]
}
}
Keep map keys stable once a VM exists — renaming a key causes OpenTofu to destroy and recreate that VM.
Destroying a VM or the whole environment
Review the plan carefully; never use -auto-approve for destructive changes.
tofu plan -destroy
tofu destroy
Assumptions to verify for your environment
- Node:
proxmox_nodemust match an existing Proxmox node name. - Storage:
storage_poolmust be a datastore on that node capable of holding VM disks and imported images (e.g.local-lvm, a ZFS pool, or similar). - Network bridge:
network_bridgemust be an existing bridge (e.g.vmbr0); VLAN tagging is optional per VM viavlan_id. - Cloud images:
image_urlshould point at a cloud-init-enabled qcow2/raw image (e.g. official Ubuntu/Debian cloud images). - SSH keys:
ssh_public_keymust be a public key; private keys are never used here.
None of these are assumed by default — they are required variables or per-VM fields, so review terraform.tfvars.example and set real values for your cluster.
State
State is local by default (no backend block). If more than one person or CI will run this project, configure a remote backend before continuing. Never commit *.tfstate, *.tfstate.*, or .terraform/.