If it cannot be rebuilt from code, it is not finished.
Every piece of infrastructure I run is described in a repository. Not documented after the fact — defined there, as the source the running system is reconciled against.
Infrastructure as Code — Terraform
- VM provisioning on Proxmox, including cloud-init, sizing and static addressing.
- Cloudflare DNS records and tunnel ingress rules managed through the provider and API.
- State persisted outside the workspace so pipeline runs are repeatable rather than destructive.
Providers do not always cover the API you need. The Proxmox provider cannot delete a running VM, so a destroy would fail the whole pipeline — solved by querying the VM state through the Proxmox API and stopping it first. I wrote that one up.
Configuration as Code — Ansible
- Kubernetes bootstrap with kubeadm, from bare Ubuntu to a joined cluster.
- Platform services installed in dependency order — CNI, load balancer, ingress, storage, certificates, monitoring.
- Secrets injected at deploy time from a single source, never committed.
The recurring lesson here was about timing. Playbooks that assumed a resource was ready failed intermittently and unpredictably. Playbooks that check actual state and retry until it converges do not.
Stop designing systems that depend on timing. Start designing systems that converge.
GitOps — Argo CD
Argo CD watches the repository and reconciles the cluster to match it, with automated sync, pruning and self-healing. Changing what runs in production means committing a file, not running a command.
The limit of that model is worth stating plainly, because it is where people get caught: GitOps reconciles declarations, not data. When a Helm release was removed from a namespace here, Argo CD faithfully rebuilt the namespace and the ingress — both declared in Git — and could not rebuild the release itself, because Helm-managed resources are rendered at install time and never stored in Git.
The same boundary applies to application content. Git guarantees the platform can be rebuilt. It guarantees nothing about what is on it — which is why backups came before features.
CI/CD — GitHub Actions
A self-hosted runner, because GitHub-hosted runners are cloud VMs with no route to a private LAN — they cannot reach the Proxmox API or the cluster nodes. Self-hosting solves that and keeps every credential off GitHub entirely.
Workflows are path-filtered, so infrastructure changes and application changes take different routes. That is a blast-radius control as much as a speed optimisation: a change to Kubernetes manifests deploys through Argo CD and never touches the Terraform pipeline at all.
Containerised tooling
Terraform and Ansible both run in containers, so the build executes identically on a laptop and on the CI runner. No version drift, no “works on my machine”, no host prerequisites beyond Docker.