Provisioning
Terraform + Ansible monorepo for every piece of infrastructure I own — Proxmox, Vault, Authentik, Harbor, Okta, GitHub, and more, all applied through Atlantis.
One repo that covers everything below the application layer. Terraform roots for each provider, Ansible for node configuration and cluster bootstrapping, Packer for VM images, and Atlantis running on the cluster to handle applies.
Terraform roots
Each provider gets its own root with its own state. Keeps the blast radius small and makes it easy to work on one thing without touching unrelated infrastructure.
| Root | What it manages |
|---|---|
proxmox/images | Packer-built VM templates |
proxmox/services/* | VMs for the Kubernetes cluster, lab controller, PXE server, MaaS, Ollama |
vault | Auth backends, Kubernetes auth roles, secret engines, policies |
authentik | Applications, groups, OAuth/OIDC providers |
harbor | Container registry projects, robot accounts, replication rules |
github | Org settings, repo configuration |
minio | Object storage buckets (used for Terraform state and backups) |
okta | Directory sync (experimental, not production) |
jamf | MDM configuration (experimental, not production) |
Vault Kubernetes auth roles
Every workload that needs secrets from Vault needs a Kubernetes auth role.
Rather than hardcoding these in HCL, each role is a YAML file in
terraform/vault/kubernetes-roles/. Terraform reads them all at plan time:
locals {
kubernetes_role_files = fileset(path.module, "kubernetes-roles/*.yaml")
kubernetes_roles = {
for f in local.kubernetes_role_files :
trimsuffix(basename(f), ".yaml") => yamldecode(file("${path.module}/${f}"))
}
}
module "kubernetes_auth" {
source = "./modules/kubernetes-auth"
for_each = local.kubernetes_roles
...
}Adding a new role means adding one YAML file and opening a PR. No HCL to edit, no risk of touching something unrelated.
Atlantis
All terraform apply runs go through Atlantis,
which runs on the cluster. PRs trigger plan automatically; apply runs on
merge. Nobody applies from a laptop.
The one honest awkwardness here: Atlantis runs on the Kubernetes cluster, which is itself partly provisioned by Terraform in this repo. If the cluster is down, you can't apply infrastructure changes through the normal path. In practice this hasn't been a real problem — the cluster is stable and the Proxmox roots don't depend on it — but it's a real bootstrap dependency worth knowing about.
Ansible
The Ansible side handles everything that Terraform can't: node bootstrapping, Kubespray for the Kubernetes control plane, and base OS configuration across all VMs. Kubespray is vendored in as a git subtree so the version is pinned and the whole thing is reproducible without network access at run time.
A Makefile at the repo root ties it together — make setup installs
dependencies and creates the Python venv, make kubernetes runs the full
cluster provisioning playbook.
Stack
Terraform, Ansible, Atlantis, Packer, Kubespray, Vault provider, Authentik provider, Harbor provider, GitHub provider.