Manage Hetzner control nodes and local worker (heimdall) with unified Terraform configuration using the Talos provider.
┌─────────────────────────────────────────────────────────┐
│ Terraform Configuration (Single Codebase) │
│ ├── providers.tf │
│ ├── hetzner-nodes.tf (3x control-plane) │
│ ├── local-nodes.tf (1x heimdall control+worker) │
│ └── outputs.tf (kubeconfig, talosconfig) │
│ │
│ Managed Resources: │
│ ├── Hetzner Cloud servers (via hetzner provider) │
│ ├── Talos machine configs (via talos provider) │
│ ├── Bootstrap etcd cluster │
│ └── Generate kubeconfig/talosconfig │
└─────────────────────────────────────────────────────────┘
Infrastructure as Code: Version-controlled cluster definition Reproducibility: Rebuild entire cluster from code Documentation: Configuration self-documents infrastructure Testing: Spin up staging clusters for validation Disaster Recovery: Rapid cluster recreation
terraform {
required_providers {
hcloud = {
source = "hetznercloud/hcloud"
version = "~> 1.45"
}
talos = {
source = "siderolabs/talos"
version = "~> 0.7"
}
}
}
provider "hcloud" {
token = var.hcloud_token
}
provider "talos" {}terraform/
├── providers.tf # Provider configuration
├── variables.tf # Input variables
├── hetzner-control-nodes.tf # 3x Hetzner CPX41 control nodes
├── local-worker.tf # heimdall configuration
├── talos-config.tf # Talos machine configs
├── bootstrap.tf # Cluster bootstrap
├── outputs.tf # kubeconfig, talosconfig outputs
├── terraform.tfvars.example # Example variables file
└── README.md # Usage documentation
resource "hcloud_server" "control" {
count = 3
name = "cozy-ctrl-${count.index + 1}"
server_type = "cpx41" # 8 vCPU, 16GB RAM
location = "hel1" # Helsinki
image = "ubuntu-24.04"
user_data = templatefile("${path.module}/cloud-init-talos.yaml", {
talos_version = var.talos_version
})
labels = {
role = "control-plane"
cluster = "cozystack"
}
}resource "talos_machine_configuration" "control" {
cluster_name = "cozystack"
machine_type = "controlplane"
cluster_endpoint = "https://${hcloud_server.control[0].ipv4_address}:6443"
machine_secrets = talos_machine_secrets.this.machine_secrets
config_patches = [
yamlencode({
machine = {
kubelet = {
extraArgs = {
rotate-server-certificates = "true"
}
}
}
cluster = {
network = {
cni = {
name = "none" # Cozystack manages CNI
}
}
}
})
]
}resource "talos_machine_configuration" "local_worker" {
cluster_name = "cozystack"
machine_type = "worker"
cluster_endpoint = "https://${hcloud_server.control[0].ipv4_address}:6443"
machine_secrets = talos_machine_secrets.this.machine_secrets
config_patches = [
yamlencode({
machine = {
network = {
interfaces = [{
interface = "tailscale0"
addresses = [var.heimdall_tailscale_ip]
}]
}
sysctls = {
"net.core.rmem_max" = "2500000" # SeaweedFS tuning
}
kubelet = {
extraMounts = [{
destination = "/var/mnt/seaweedfs"
type = "bind"
source = "/mnt/seaweedfs"
options = ["bind", "rw"]
}]
}
}
})
]
}cd terraform/
# Initialize Terraform
terraform init
# Review planned changes
terraform plan
# Apply configuration
terraform apply
# Extract kubeconfig
terraform output -raw kubeconfig > ~/.kube/cozy-stack-kubeconfig
# Extract talosconfig
terraform output -raw talosconfig > ~/.talos/cozy-stack-talosconfig- Modify
hetzner-control-nodes.tfto increase count - Run
terraform planto preview changes - Run
terraform applyto provision new node - Talos automatically joins cluster
- Decrease count in configuration
- Run
terraform apply - Terraform drains and destroys node
# Cluster lost, rebuild from code
cd terraform/
terraform destroy # Clean slate
terraform apply # Rebuild identical cluster
# Restore application state from backups
kubectl apply -f manifests/terraform.tfvars:
hcloud_token = "xxxxxxxxxxxxx" # Store in Bitwarden
talos_version = "v1.10.5"
cluster_endpoint = "cozy.homi.zone"
heimdall_tailscale_ip = "100.x.x.x/32"
control_nodes = {
count = 3
type = "cpx41"
location = "hel1"
}Remote State: Store Terraform state in S3-compatible backend (SeaweedFS)
terraform {
backend "s3" {
bucket = "terraform-state"
key = "cozystack/terraform.tfstate"
endpoint = "https://s3.cozy.homi.zone"
skip_credentials_validation = true
skip_region_validation = true
}
}State Locking: Prevent concurrent modifications
Secrets Management:
- Hetzner API token → Bitwarden Secrets Manager
- Talos secrets → Generated by provider, stored in state
- Never commit
terraform.tfvarsto git
State Encryption: Enable S3 backend encryption
Terraform provisions bare Talos cluster. Post-deployment:
- Install Cozystack via Helm (automated in bootstrap.tf)
- Apply GitOps manifests for applications
- Cozystack manages application lifecycle
- Add Vault integration for secret management
- Implement blue/green deployment for node rotation
- Add monitoring for Terraform drift detection
- Create CI/CD pipeline for automated plan/apply
- Add cost estimation via Infracost