Skip to content

Provisioning

The cluster is provisioned using OpenTofu, an open-source Terraform-compatible IaC tool. All infrastructure state is stored in PostgreSQL.

NAS Dependencies

The following services must be running on the NAS before cluster provisioning can begin. These are not managed by OpenTofu — they are preconditions.

Service Why it's needed
Vault OpenTofu reads secrets from Vault during provisioning (Gitea token, OIDC client ID, ESO token). Without it, tofu plan fails immediately.
PostgreSQL Stores OpenTofu state. Required to initialize the backend.
Gitea The repository that Flux will watch must exist and be accessible before taloser-flux runs.
Pi-hole DNS must resolve internal hostnames (e.g. vault.hdhomelab.com, gitea.hdhomelab.com, pve2.hdhomelab.com) for providers and modules to connect.
MinIO S3-compatible object storage used by cluster workloads. Runs on the NAS; buckets are managed by the buckets OpenTofu deployment.

Rebuilding from scratch

Bring up these NAS services first, then proceed with cluster provisioning.

Repository Structure

tofu/
├── tf-deploy/          # Deployment configurations (what to create)
│   ├── noah/           # Kubernetes cluster
│   ├── psql/           # PostgreSQL databases and roles
│   ├── buckets/        # MinIO S3 buckets
│   └── authentik/      # Authentik SSO applications
└── tf-modules/         # Reusable modules (how to create it)
    ├── taloser/        # Talos VMs on Proxmox
    ├── taloser-k8s/    # Core k8s infrastructure (Cilium, ESO)
    ├── taloser-flux/   # Flux CD bootstrap
    ├── psql/           # PostgreSQL role/database management
    ├── minio-bucket/   # MinIO bucket management
    ├── authentik-oidc/ # Authentik OIDC provider
    └── authentik-ldap/ # Authentik LDAP provider

Cluster Provisioning (tf-deploy/noah)

The cluster deployment is split into three modules that run in sequence:

graph LR
    A[taloser\nVMs + Talos config] --> B[taloser-k8s\nCilium + ESO + VPA]
    B --> C[taloser-flux\nFlux CD bootstrap]
Hold "Alt" / "Option" to enable pan & zoom

Each module depends on the previous one being fully ready — this isn't just ordering, it's a hard bootstrap dependency chain:

Why this order?

  • taloser must run first because there is no cluster until VMs exist and Talos is configured. Subsequent modules need the kubeconfig output from this step to connect to the API server.
  • taloser-k8s must run before Flux because it installs the things Flux-managed workloads depend on at reconcile time: Cilium (without it, pods can't communicate and nothing reaches Ready), ExternalSecrets Operator (without it, any ExternalSecret resource Flux creates would error immediately), and VPA (CRDs must exist before Flux applies VerticalPodAutoscaler objects in app manifests).
  • taloser-flux runs last because it hands control to GitOps. Once Flux is bootstrapped it begins reconciling the repository, so the platform must already be stable before that happens.

Provisions Proxmox VMs and applies Talos Linux configuration.

  • Downloads the Talos image from factory.talos.dev to each Proxmox node
  • Creates VMs with the specified CPU, RAM, disks, and MAC addresses
  • Applies Talos machine configs (control plane / worker)
  • Outputs kubeconfig and Talos client config

Node configuration is defined in locals.tf — see Cluster for the full node table.

Installs core Kubernetes infrastructure via Helm:

  • Cilium — CNI, L2 LoadBalancer, Gateway API, Ingress Controller
  • ExternalSecrets Operator — syncs secrets from Vault into Kubernetes
  • Vertical Pod Autoscaler — installs VPA controller and CRDs; by running here, all Flux-managed VerticalPodAutoscaler objects reconcile on first apply without any ordering dependency

Bootstraps Flux CD onto the cluster:

  • Installs Flux controllers (including image-reflector and image-automation)
  • Configures Flux to watch the flux branch of the Gitea repository
  • Uses a Gitea token from Vault for authentication

State Backend

State is stored in PostgreSQL on the NAS. Initialize with:

cd tofu/tf-deploy/noah
tofu init -backend-config=backend.pg.tfbackend

Note

For validation without connecting to the backend (e.g. in CI):

tofu init -backend=false

Providers

Provider Purpose
bpg/proxmox Create and manage Proxmox VMs
fluxcd/flux Bootstrap Flux CD
go-gitea/gitea Configure Gitea repository for Flux
hashicorp/vault Read secrets during provisioning
hashicorp/kubernetes Apply k8s resources
hashicorp/helm Install Helm charts

Other Deployments

psql (tf-deploy/psql)

Manages PostgreSQL databases and roles on the shared NAS database. Uses the psql module to create per-app databases, users, and grants. Credentials are generated at provision time and written directly to Vault — no init containers or manual setup needed.

See PostgreSQL Provisioning for the full pattern, module design, and how to add a new database.

buckets (tf-deploy/buckets)

Manages MinIO S3 buckets using the minio-bucket module.

authentik (tf-deploy/authentik)

Manages Authentik SSO applications using the authentik-oidc and authentik-ldap modules — creates OIDC providers and LDAP configurations for apps that need SSO.

Running a Plan

cd tofu/tf-deploy/noah
tofu init -backend-config=backend.pg.tfbackend
tofu plan -out plan.out
tofu apply plan.out