How to stand up an environment from nothing. The cluster is built by Terragrunt in module order, then handed to Flux. Run everything through mise so tool versions and 1Password credential injection are consistent. See the README for the repository layout — where each module and manifest lives, and where state and secrets come from.
-
Create the environment's 1Password vault (
o11y_tf_stagingoro11y_tf_prod). -
Set up
.private/openstack/<env>/openrc.shfor the environment's OVH Public Cloud project. -
Upload the Talos OpenStack image (control planes only) to the environment's regions:
mise run //deployment/modules/ovh/account:image:download && mise run //deployment/modules/ovh/account:image:uploadThis image carries the
qemu-guest-agent+netbirdschematic. -
Workers need no download or upload — they are OVH BYOI and pull the bare-metal raw straight from the Talos Factory at order time. The worker schematic must stay netbird-only:
qemu-guest-agenton bare metal blocks on a virtio port that never appears and reboot-loops the node. -
For production: delete the apex DNS records via the OVH dashboard before applying.
Set the environment in the shell first:
export ENVIRONMENT=staging
export TF_VAR_env=staging-
OVH — cloud project, vRack, private network, CP instances, workers, IPLB, DNS. First-time runs are slow (CP instances ~5 min each, bare-metal orders 20–45 min each, IPLB ~10 min).
mise run //deployment/modules/ovh/account:apply
-
NetBird — the per-environment mesh objects (all named
o11y-<env>-*): groups and setup keys for the Talos nodes and the in-cluster routing peers, the vRack network route, the mesh-gateway VIP resource and DNS zone, the pod-egress network, and the access policies (yucca → resource,yucca → gateway,talos → bootstrap opc). The Talos module consumes the node setup key and mesh zone from here, so apply NetBird first.mise run //deployment/modules/netbird/cluster:apply
-
NetBox — registers the environment's ranges (vRack, gateway ServiceCIDR, pod-egress CIDR) in IPAM, from the same values the other modules allocate.
mise run //deployment/modules/netbox/cluster:apply
-
Talos (bootstrap) — initial bring-up over public IPs, because the NetBird extension isn't running yet.
TF_VAR_use_public_endpoints=true mise run //deployment/modules/talos/cluster:apply
-
Verify the cluster is up and operator-side NetBird routing works. Pull the configs (see Cluster access) and hit the APIs over the NetBird network — use the direct kubeconfig context during bring-up, since the default context targets the mesh gateway, which only exists once Flux has reconciled:
mise run //deployment/modules/talos/cluster:kubeconfig && mise run //deployment/modules/talos/cluster:talosconfig kubectl --kubeconfig .private/$ENVIRONMENT/kubeconfig --context o11y-$ENVIRONMENT-direct get nodes -o wide talosctl --talosconfig .private/$ENVIRONMENT/talosconfig -n 10.150.200.10 get members
-
Talos (steady state) — drop the public-endpoints override now that NetBird routes work; the host firewall closes the public NIC (everything except
:30443on workers).unset TF_VAR_use_public_endpoints mise run //deployment/modules/talos/cluster:apply -
Kubernetes/Helm — install CoreDNS (Terraform-seeded — Flux needs cluster DNS from its first reconcile; Talos's copy is disabled), the Flux Operator + Instance, the
bootstrap-settingsConfigMap, and the bootstrap secrets (cert-manager OVH DNS credentials, the 1Password Connect token for external-secrets). After this, Flux owns cluster state.mise run //deployment/modules/kubernetes/helm:apply
Flux then reconciles from kubernetes/clusters/<env>/apps.yaml, fanning out to the per-app Kustomizations in dependency order.
How to get kubectl / talosctl access to an existing cluster (no bootstrap required).
Prerequisites:
- NetBird — the cluster APIs are reachable only over the NetBird network, so your host must be running the NetBird client (
netbird up) and joined to the FUTO NetBird account, which places your peer in theyuccagroup. The access policy then distributes the route to the cluster's vRack CIDR, sokubectl/talosctlcan reach the nodes' private IPs. - 1Password CLI (
op) — installed and signed in to theteam-futo.1password.comaccount.mise run //deployment/modules/talos/cluster:kubeconfigfetches the configs through the//:tgterragrunt wrapper, which wrapsop runto inject the Terraform state credentials; without an authenticatedopit can't read state.
Fetch the configs. Two tasks pull kubeconfig and talosconfig for the environment (run whichever you need):
export ENVIRONMENT=staging # or production
export TF_VAR_env=$ENVIRONMENT
mise run //deployment/modules/talos/cluster:kubeconfig # for kubectl
mise run //deployment/modules/talos/cluster:talosconfig # for talosctlEach writes to .private/$ENVIRONMENT/ (mode 600) from the Talos module's Terraform outputs. The kubeconfig is TF-authored with two contexts:
o11y-<env>(default) — the HA endpointkube.<mesh-zone>:6443, fronted by the Envoy mesh gateway (TLS passthrough to every apiserver). Survives any single CP being down and never hairpins through a NetBird routing peer.o11y-<env>-direct— a control-plane private IP, for bootstrap/DR before the mesh gateway exists:kubectl --context o11y-<env>-direct. Every CP IP is an apiserver cert SAN, so TLS validates on both paths. (The floating VIP.5is in-cluster-only — it doesn't ARP across DCs.)
Point your tools at them:
export KUBECONFIG=$PWD/.private/$ENVIRONMENT/kubeconfig
export TALOSCONFIG=$PWD/.private/$ENVIRONMENT/talosconfig
kubectl get nodes
talosctl -n 10.150.200.10 health # any CP private IP; the talosconfig also lists the workers| Task | Command |
|---|---|
| Plan/apply one module | mise run //deployment/modules/<m>:plan / mise run //deployment/modules/<m>:apply (or mise run :plan from inside the module) |
| Plan/apply all in dep order | mise run //deployment:plan / mise run //deployment:apply |
| Re-init backends | mise run //deployment:init |
| Any other terragrunt subcommand | mise run //:tg <args> (runs in the current directory, e.g. state rm, import, force-unlock) |
| Format HCL / Terraform | mise run //deployment:fmt |
| Lint docs | mise run //:md:lint |
| Fetch kubeconfig / talosconfig | mise run //deployment/modules/talos/cluster:kubeconfig / mise run //deployment/modules/talos/cluster:talosconfig (see Cluster access) |
- OpenTofu + Terragrunt for IaC; mise drives tool versions and task wrappers. Version pins live in
.mise/config.tomland each module's lock file. - 1Password CLI (
op run --env-file deployment/.env) injects API credentials at invocation time.