Part 1 – AI-Driven Deployment with Terraform and Goose
This is where the experiment begins. Instead of manually writing Terraform code and provisioning VMs the traditional way, the goal is to hand a spec document to an AI agent and see how far it gets on its own.
The short version: it got further than expected, broke in interesting ways, and the failure modes were just as useful as the successes.
Tools Used
- Goose — open-source AI agent framework by Block. Runs locally, supports MCP extensions, and can execute shell commands, write files, and run arbitrary tools.
- DeepSeek V3 via OpenRouter — the model driving the agent. OpenRouter gives access to a wide range of models through a single OpenAI-compatible API endpoint, cost-effective for Terraform and Ansible generation tasks.
- Claude Code — used for reviewing generated infrastructure code and more complex reasoning tasks.
- Terraform with the bpg/proxmox provider — handles VM provisioning via the Proxmox API.
Prerequisites
Before the agent can do anything, a few things need to be in place manually.
On Proxmox:
# Create a dedicated API user for the agent
pveum user add goose@pve --comment "Goose automation"
pveum aclmod / --user goose@pve --role Administrator
pveum user token add goose@pve automation --privsep 0Start with the Administrator role for initial setup. Scope it down once everything is working — debugging permission issues while also debugging agent behaviour is frustrating.
On your workstation:
# Install Terraform
brew install terraform
# Create project structure
mkdir -p ~/sovereign-stack/{terraform,ansible,docker,goose,docs}
# Create SSH key for agent access to Proxmox host
ssh-keygen -t ed25519 -C "sovereign-stack-agent" -f ~/.ssh/id_ed25519_goose
ssh-copy-id -i ~/.ssh/id_ed25519_goose.pub root@<proxmox-ip>Goose setup:
Install Goose desktop or CLI from github.com/block/goose. Configure it with DeepSeek via OpenRouter:
- Provider: OpenAI-compatible
- Host:
https://openrouter.ai/api/v1 - Model:
deepseek/deepseek-chat-v3-5 - API key: your OpenRouter key
Enable the Developer extension — this gives Goose shell access to run Terraform, Ansible, and arbitrary commands locally.
The Terraform Provider — A Non-Obvious Requirement
The bpg/proxmox provider needs both API access and SSH access to the Proxmox host. API alone is not enough — it uses SSH for disk operations. Your versions.tf needs an SSH block:
terraform {
required_providers {
proxmox = {
source = "bpg/proxmox"
version = "~> 0.73"
}
}
}
provider "proxmox" {
endpoint = "https://<proxmox-ip>:8006/"
api_token = "goose@pve!automation=<your-token>"
insecure = true
ssh {
agent = false
username = "root"
private_key = file("~/.ssh/id_ed25519_goose")
}
}Without the ssh {} block, you get this error when applying:
Error: creating custom disk: unable to authenticate user "" over SSHVerifying the Connection
Before handing anything to the agent, verify Terraform can reach Proxmox:
data "proxmox_virtual_environment_nodes" "available_nodes" {}
output "nodes" {
value = data.proxmox_virtual_environment_nodes.available_nodes.names
}cd terraform
terraform init
terraform plan
# Expected output: nodes = ["pvegb"]If you see your node name, the connection works. Then hand it to the agent.
The Agent Prompt
The agent was given a spec document describing the full project and a focused first task:
You are deploying the Sovereign Stack – a fully open-source alternative
to Microsoft 365, managed by AI agents on Proxmox.
Read the full project spec at:
/path/to/sovereign-stack/docs/spec.md
Your first task is Phase 1, Step 1:
Write Terraform code to provision the OPNsense VM on Proxmox:
- VM ID: 100, Name: opnsense
- RAM: 2GB, Disk: 20GB on intelpool
- ISO: OPNsense-25.1-dvd-amd64.iso (already on host)
- Network: vmbr0 bridge
Do NOT apply yet — show me terraform plan output first and
wait for my approval before running terraform apply.
Constraints:
- Never touch rpool
- Never delete ISOs
- Always ask before applyingWhat the Agent Did
The agent worked through the task methodically — reading the spec, inspecting existing files, generating Terraform code, running terraform plan, and iterating on errors.
Round 1 — First attempt
The agent generated a working VM resource but with issues it caught on the next run:
- Used deprecated
enabledattribute in thecdromblock - Set
operating_system type = "l26"(Linux) — wrong for OPNsense which is FreeBSD-based boot_orderreferencedscsi0when the disk interface wasvirtio0
Round 2 — Self-correction
The agent fixed all three issues without being told. Clean plan. At this point human review flagged two more things:
- The
initialization {}block (cloud-init) doesn’t work on OPNsense/FreeBSD - The
agent {}block (QEMU Guest Agent) isn’t supported on FreeBSD
Round 3 — Permission and storage name errors
terraform apply failed with storage permission errors. The agent correctly identified the token needed storage permissions — but assumed the ISO storage was named local. On this host, it is named isos.
Check actual storage names before writing the spec:
pvesm status
# Shows: intelpool (zfspool, active), isos (dir, active), local (dir, disabled)Fix — update the ISO path variable:
opnsense_iso_path = "isos:iso/OPNsense-25.1-dvd-amd64.iso"And add the missing permission:
pveum aclmod /storage/isos --user goose@pve --role PVEDatastoreUserRound 4 — Disk format error
Error: creating custom disk: unable to parse volume ID 'intelpool:'The agent had set file_id = "intelpool:vm-100-disk-0". For new disks, file_id should be omitted — Proxmox creates it automatically:
disk {
datastore_id = var.storage_pool
size = 20
interface = "virtio0"
iothread = true
discard = "on"
}Final apply — success:
proxmox_virtual_environment_vm.opnsense: Creation complete after 3s [id=100]
Apply complete! Resources: 1 added, 0 changed, 1 destroyed.Where the Agent Needed Human Input
| Issue | Agent | Human needed |
|---|---|---|
Deprecated cdrom attribute |
Fixed autonomously | — |
| Wrong OS type (l26 vs other) | Fixed after being told | One correction |
| cloud-init on FreeBSD | Fixed after being told | One correction |
| Wrong storage name (local vs isos) | Couldn’t know — not in spec | Check pvesm status, update spec |
| Missing SSH block in provider | Couldn’t know — provider quirk | Add ssh {} block |
file_id for new disks |
Fixed after being told | One correction |
The Loop Problem
One failure worth documenting: when the agent hit a permission error, it ran terraform apply repeatedly without getting new information. It also started switching strategy — from Terraform to bash scripts on the Proxmox host.
Lesson: AI agents need explicit stopping conditions. Adding something like “if you hit the same error twice, stop and explain what you need rather than retrying” to the prompt prevents this. This goes into the spec for all future sessions.
OPNsense Initial Setup
After terraform apply, the VM boots from the ISO automatically. Connect via the Proxmox web console and complete the installation:
- Select Install (UFS) — no ZFS needed in a VM
- Select the
vtbd0disk (the 20GB virtio disk) - Set a root password and reboot
- Remove the ISO from the CD-ROM drive before reboot
After boot, set the management IP via the console menu (option 2):
- Interface: LAN (vtnet0)
- Static IP:
10.10.0.1/24 - No DHCP, no upstream gateway
OPNsense web UI is then available at https://10.10.0.1 from a client on VLAN 100.
What’s Next
Part 2 covers provisioning the Proxmox Backup Server VM and configuring the storage pools — again driven by the agent, with the lessons from Part 1 baked into the spec.