← All projects

Provisioning

Terraform + Ansible monorepo for every piece of infrastructure I own — Proxmox, Vault, Authentik, Harbor, Okta, GitHub, and more, all applied through Atlantis.

·
terraformansiblevaultauthentikatlantis

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.

RootWhat it manages
proxmox/imagesPacker-built VM templates
proxmox/services/*VMs for the Kubernetes cluster, lab controller, PXE server, MaaS, Ollama
vaultAuth backends, Kubernetes auth roles, secret engines, policies
authentikApplications, groups, OAuth/OIDC providers
harborContainer registry projects, robot accounts, replication rules
githubOrg settings, repo configuration
minioObject storage buckets (used for Terraform state and backups)
oktaDirectory sync (experimental, not production)
jamfMDM 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.