Platform & Orchestration

Terraform

Infrastructure from declared state - one workspace per VM so a mistake blasts one machine, not the fleet, and a pipeline that rolls forward cleanly when a step fails.

What it is & why I use it

Terraform is how VMs come into existence in AutomationLab. The hypervisor is driven entirely through the bpg/proxmox provider - there is no hand-built guest anywhere in the lab. Terraform owns the "make the machine exist and match what the code says" half of provisioning; configuration management takes it from there.

I use it because infrastructure should be declarative and diffable. A plan shows exactly what will change before anything does, and drift surfaces as a diff instead of a surprise - which is the whole reason to treat infrastructure as code rather than as a sequence of console clicks.

How I use it in the lab

  • One workspace per VM. Each machine has its own workspace and state rather than one giant shared state for the whole lab. A bad apply can only touch the one VM it owns.
  • Driven by the pipeline. Terraform runs as the provision stage of the Jenkins flow, after IP allocation and before configuration - plan, gated apply, then hand off.
  • Roll-forward on partial failure. If a later stage fails, the pipeline cleans up forward to a consistent state rather than leaving a half-built VM lying around, and a decommission path reverses what was created.
  • Pinned provider and tool versions. The provider and the Terraform binary are pinned to explicit versions so a run is reproducible and does not silently change behavior when an upstream release lands.

Architecture

Diagram coming Per-VM workspace model: allocate -> plan -> gated apply -> hand off, each VM isolated in its own state. Scrubbed of addresses and hostnames.

In action

Screenshot coming A plan output for a single VM workspace ahead of a gated apply. Captured per the publishing checklist - addresses, hostnames, and IDs redacted.

Lessons learned & gotchas

Gotcha - a version in the listing is not a downloadable artifact When pinning a tool version, "the version appears in the releases directory" is not the same as "the release exists." A version can be listed while the specific artifact you need returns a 404. So I verify the actual artifact URL returns 200 before pinning to it - not just that the version number shows up in a directory listing. Skip that and a pipeline that passed yesterday breaks on a fresh agent that has to download the pinned binary for the first time.
  • Per-VM state is worth the extra files. One monolithic state for everything is tidy until a single bad apply or a lock left behind takes the whole lab's provisioning hostage. Small, isolated states keep the blast radius to one machine.
  • The plan is the review, so gate the apply. Applying straight from a push defeats the point of a plan. A human-gated apply in the pipeline means the diff is actually looked at before infrastructure moves.
  • Roll-forward beats leaving wreckage. A failed provision that cleans up to a known state is recoverable; a half-built VM with orphaned state is a manual mess. Write the failure path, do not just hope for the happy one.

Impact

1 VM
per workspace / state
100%
guests from code
200
verified before pinning

Terraform is what makes the lab reproducible from the metal up: every VM is described in code, isolated in its own state, and created through a gated, roll-forward pipeline. The version-pinning lesson is a small one that saves a whole class of "worked yesterday, broken today" failures - proof that in IaC the boring details are exactly where reliability lives.

Code & further reading

Published modules are scrubbed of internal addresses, hostnames, IDs, and any credential references before they go public.