The root .gitignore excluded .terraform.lock.hcl and every *.tfvars, which hid two things that belong in version control: - .terraform.lock.hcl pins the provider hashes. versions.tf tracks Telmate/proxmox 3.0.2-rc05, a release candidate, so the version constraint alone is not enough if that tag is ever re-published. - terraform.tfvars held one real secret (proxmox_api_token_secret) plus the entire vms map — 7 VMs with their vmids, sizes and static IPs. That is infra definition, and it existed only on one laptop. Meanwhile the committed terraform.tfvars.example still advertised web1/db1. Split at the credential boundary: the provider auth triple stays in the gitignored terraform.tfvars, everything else moves to vms.auto.tfvars, which is committed and auto-loaded (no -var-file needed). terraform.tfvars.example is now credentials-only. `tofu plan` reports no changes. State stays ignored — it carries cloud-init attributes and should not be in git. Noted in the README that it has no remote backend, and that state manages two VMs (bastion-box, nonkeiwaisi-box) the map does not declare. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| .terraform.lock.hcl | ||
| main.tf | ||
| provider.tf | ||
| README.md | ||
| terraform.tfvars.example | ||
| variables.tf | ||
| versions.tf | ||
| vms.auto.tfvars | ||
Nodito VMs with OpenTofu (Proxmox)
This directory lets you declare VMs on the nodito Proxmox node and apply with OpenTofu. It clones the Ansible-built template debian-13-cloud-init and places disks on the ZFS pool proxmox-tank-1.
Prereqs
- Proxmox API token with VM privileges. Example: user
root@pam, token nametofu. - OpenTofu installed.
sudo apt-get update sudo apt-get install -y apt-transport-https ca-certificates curl gnupg sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://get.opentofu.org/opentofu.gpg | sudo tee /etc/apt/keyrings/opentofu.gpg >/dev/null curl -fsSL https://packages.opentofu.org/opentofu/tofu/gpgkey | sudo gpg --no-tty --batch --dearmor -o /etc/apt/keyrings/opentofu-repo.gpg >/dev/null sudo chmod a+r /etc/apt/keyrings/opentofu.gpg /etc/apt/keyrings/opentofu-repo.gpg echo \ "deb [signed-by=/etc/apt/keyrings/opentofu.gpg,/etc/apt/keyrings/opentofu-repo.gpg] https://packages.opentofu.org/opentofu/tofu/any/ any main deb-src [signed-by=/etc/apt/keyrings/opentofu.gpg,/etc/apt/keyrings/opentofu-repo.gpg] https://packages.opentofu.org/opentofu/tofu/any/ any main" | \ sudo tee /etc/apt/sources.list.d/opentofu.list > /dev/null sudo chmod a+r /etc/apt/sources.list.d/opentofu.list sudo apt-get update sudo apt-get install -y tofu tofu version - The Ansible template exists:
debian-13-cloud-init(VMID 9001 by default).
Provider Auth
Credentials are the only thing not in git. Copy terraform.tfvars.example to
terraform.tfvars (gitignored) and set:
proxmox_api_url(e.g.https://nodito:8006/api2/json)proxmox_api_token_id(e.g.root@pam!tofu)proxmox_api_token_secret
Alternatively, export them as TF_VAR_proxmox_api_token_secret etc.
Declare VMs
VMs are declared in vms.auto.tfvars, which is committed. *.auto.tfvars is
loaded automatically, so it needs no -var-file. Example entry:
vms = {
web1 = {
name = "web1"
cores = 2
memory_mb = 2048
disk_size_gb = 20
ipconfig0 = "ip=dhcp" # or "ip=192.168.1.50/24,gw=192.168.1.1"
}
}
All VM disks are created on zfs_storage_name (defaults to proxmox-tank-1). Network attaches to vmbr0. VLAN can be set per-VM with vlan_tag.
Usage
tofu init
tofu plan
tofu apply
terraform.tfvars and vms.auto.tfvars are both auto-loaded.
VMs are created once and then protected: the module sets
lifecycle.prevent_destroy = trueand ignores subsequent config changes. After the initial apply, manage day‑2 changes directly in Proxmox (or remove the lifecycle block if you need OpenTofu to own ongoing updates).
Notes
- Clones are full clones by default (
full_clone = true). - Cloud-init injects
cloud_init_userandssh_authorized_keys. .terraform.lock.hclis committed: it pins the provider hashes, which matters becauseversions.tftracks a release candidate (3.0.2-rc05).- State is local (
terraform.tfstate, gitignored) and has no remote backend, so it exists only on the machine that last rantofu apply. - The map is not a complete inventory of nodito: state also manages
bastion-box(1100) andnonkeiwaisi-box(3300), which are not declared invms.auto.tfvars.tofu planis clean today, but relaxing thelifecycleblock without first declaring them would put them up for destruction. - Disks use
scsi0on ZFS withdiscardenabled.