Files
spinifex/scripts/iac/proxmox

Proxmox IaC

OpenTofu/Terraform templates for provisioning Hive development clusters on Proxmox VE.

Prerequisites

Quick Start (hive-test.sh)

The hive-test.sh wrapper script manages the full lifecycle: provision, configure, test, and destroy.

# Source environment
source scripts/iac/proxmox/.env
source ~/prox   # Proxmox API token

# Provision a 3-node cluster
./scripts/iac/hive-test.sh up my-cluster

# Configure: clone repo, build, form cluster, start services
./scripts/iac/hive-test.sh configure my-cluster

# Run smoke tests
./scripts/iac/hive-test.sh test my-cluster

# Check cluster health
./scripts/iac/hive-test.sh status my-cluster

# SSH to a specific node
./scripts/iac/hive-test.sh ssh my-cluster 2

# Destroy when done
./scripts/iac/hive-test.sh down my-cluster

# Or run the full lifecycle (up → configure → test → down)
./scripts/iac/hive-test.sh full my-cluster

Options

--node-count=N       Number of VMs (default: 3)
--memory-mb=N        Memory per VM in MB (default: 16384)
--cpu-cores=N        CPU cores per VM (default: 4)
--disk-size-gb=N     Disk size per VM in GB (default: 32)

Example with custom sizing:

./scripts/iac/hive-test.sh up my-cluster --node-count=5 --memory-mb=8192 --cpu-cores=2

Per-cluster state is stored in scripts/iac/proxmox/clusters/<name>/terraform.tfstate, allowing multiple clusters to coexist.

Manual Setup

1. Configure environment

cp .env.example .env
# Edit .env with your Proxmox endpoint, SSH keys, and node configuration

2. Set API token

export PROXMOX_VE_API_TOKEN="terraform@pve!provider=YOUR_TOKEN_SECRET"

3. Deploy

source .env
tofu init
tofu plan -var="cluster_name=dev1"
tofu apply -var="cluster_name=dev1"

Example .env file

# --- Proxmox API ---

# API token - set manually via: export PROXMOX_VE_API_TOKEN="terraform@pve!provider=..."
# Format: <user>@<realm>!<token-name>=<token-secret>

# Proxmox VE API endpoint (the URL you use to access the web UI)
export TF_VAR_proxmox_endpoint="https://pve1.lab.example.com:8006/"

# --- SSH Access ---

# SSH username on the Proxmox hosts (used by the provider for file uploads via SCP)
export TF_VAR_proxmox_ssh_username="terraform"

# Path to SSH private key that authenticates as the above user on each Proxmox host
export TF_VAR_ssh_private_key_path="~/.ssh/proxmox-tf"

# Path to SSH public key injected into VMs via cloud-init (the tf-user account)
export TF_VAR_ssh_public_key_path="~/.ssh/proxmox-tf-cloudinit.pub"

# --- Proxmox Nodes ---
#
# Physical Proxmox hosts where VMs are placed (round-robin).
# Each node object:
#   name         - Proxmox node name exactly as shown in the Proxmox UI sidebar
#   address      - SSH-reachable hostname or IP of the Proxmox host
#   bridge       - Linux bridge for VM network interfaces (check Proxmox UI > Node > Network)
#   datastore_id - Proxmox storage pool for VM disks (check Proxmox UI > Node > Storage)

export TF_VAR_nodes='[
  {
    "name": "pve1",
    "address": "pve1.lab.example.com",
    "bridge": "vmbr0",
    "datastore_id": "local-lvm"
  },
  {
    "name": "pve2",
    "address": "pve2.lab.example.com",
    "bridge": "vmbr0",
    "datastore_id": "local-lvm"
  },
  {
    "name": "pve3",
    "address": "pve3.lab.example.com",
    "bridge": "vmbr0",
    "datastore_id": "local-lvm"
  }
]'

Environment variable reference

Variable Required Description
PROXMOX_VE_API_TOKEN Yes Proxmox API token (read by provider directly)
TF_VAR_proxmox_endpoint Yes Proxmox VE API URL (e.g. https://host:8006/)
TF_VAR_proxmox_ssh_username No SSH user on Proxmox hosts (default: terraform)
TF_VAR_ssh_private_key_path Yes SSH private key for Proxmox host access
TF_VAR_ssh_public_key_path Yes SSH public key injected into VMs via cloud-init
TF_VAR_nodes Yes JSON array of Proxmox node objects

Each node object in TF_VAR_nodes:

Field Example Description
name pve1 Proxmox node name (as shown in UI sidebar)
address pve1.lab.example.com SSH-reachable hostname or IP
bridge vmbr0 Network bridge for VM interfaces
datastore_id local-lvm Storage pool for VM disks

Cluster variables (passed via -var or TF_VAR_)

Variable Default Description
cluster_name (required) Cluster name — used in VM names and tags
node_count 3 Number of VMs to create (1-10)
cpu_cores 4 CPU cores per VM
memory_mb 16384 Memory per VM in MB
disk_size_gb 32 Disk size per VM in GB
os_image local:iso/debian-12-... Proxmox image for boot disk

Destroy

source .env
tofu destroy -var="cluster_name=dev1"

SSH access

Connect to provisioned VMs using the cloud-init public key:

ssh -i ~/.ssh/your-cloud-init-key tf-user@<VM_IP>

Known issues

  • ~/hive/config/hive.toml - Does not add node1, node2, node3 from config automatically
  • ~/hive/config/predastore/predastore.toml - Uses previous static local node config, needs to use the IPs for each node in the cluster
  • On multi-node deployments, NATS on the primary can timeout waiting for other nodes to start, causing a race condition where NATS fails and all dependent services fail
  • When adding 3 nodes, nats.conf is not updated with the 3rd node. Each node's cluster name is hardcoded to C1 instead of per-node names