Skip to content

๐Ÿš€ Hetzner Kubernetes Cluster Setup Guide

This guide walks you through setting up a complete Kubernetes cluster on Hetzner Cloud using Talos Linux and Flux GitOps.

๐Ÿ“‹ Prerequisites

Quick Validation

Before starting, run our prerequisites validation script to ensure all required tools are installed:

For Linux/macOS/WSL:

# Make the script executable and run it
chmod +x scripts/validate-prerequisites.sh
./scripts/validate-prerequisites.sh

For Windows PowerShell:

# Run the PowerShell validation script
.\scripts\validate-prerequisites.ps1

For Windows users - Recommended approach: We strongly recommend using Windows Subsystem for Linux (WSL) for the best experience with Kubernetes tools:

# Install WSL (requires restart)
wsl --install

# After restart, open WSL and run the bash version
chmod +x scripts/validate-prerequisites.sh
./scripts/validate-prerequisites.sh

This script will check for all required tools, validate versions, and provide installation guidance for any missing components.

Required Tools

  1. Terraform CLI - Follow the Installation Guide
  2. Hetzner Cloud Account - Sign up here
  3. GitHub Account - For GitOps repository access
  4. kubectl CLI - Installation guide
  5. talosctl CLI - Installation guide
  6. Talos OS Snapshot - Custom Hetzner Cloud snapshot (see Step 1)

๏ฟฝ Step 1: Create Talos OS Snapshot

The terraform configuration requires a custom Talos OS snapshot in your Hetzner Cloud project.

1.1 Create Talos VM

  1. In Hetzner Cloud Console, create a new server
  2. Choose Ubuntu 22.04 as the base image
  3. Select cx11 (cheapest option for snapshot creation)
  4. Choose location Nuremberg (nbg1)
  5. Create the server and wait for it to boot

1.2 Install Talos OS

SSH into the server and install Talos:

# SSH to your temporary server
ssh root@YOUR_SERVER_IP

# Download and install Talos
curl -Lo /tmp/talos.raw.xz https://github.com/siderolabs/talos/releases/download/v1.8.0/hcloud-amd64.raw.xz
xz -d /tmp/talos.raw.xz
dd if=/tmp/talos.raw of=/dev/sda && sync
reboot

1.3 Create Snapshot

  1. Wait for the server to reboot (it will become unreachable - this is normal)
  2. In Hetzner Console, go to your server โ†’ Images tab
  3. Click "Create Image"
  4. Choose "Snapshot" type
  5. Enter description: "Talos OS v1.8.0"
  6. Click "Create Image"
  7. Wait for snapshot creation to complete
  8. Note the Snapshot ID (you'll need this for terraform)
  9. Delete the temporary server to save costs

1.4 Update Terraform Configuration

Update the snapshot ID in your terraform configuration:

cd terraform/environments/hetzner-mgmt-cluster/1-bootstrap

Edit main.tf and replace the hardcoded snapshot ID 336987024 with your new snapshot ID:

# Find these lines and update with your snapshot ID:
image = "YOUR_SNAPSHOT_ID_HERE" // Talos snapshot ID

โš ๏ธ Important: The current configuration uses hardcoded snapshot ID 336987024 in main.tf. You have two options:

  1. Create your own Talos snapshot (recommended) and update the terraform files
  2. Use the existing snapshot ID (if accessible in your Hetzner project)

Alternative: You can make the snapshot ID configurable by adding it as a terraform variable (see troubleshooting section for how to do this).


๏ฟฝ๐Ÿ”ง Step 2: Hetzner Cloud Project Setup

2.1 Create Hetzner Cloud Project

  1. Log into the Hetzner Cloud Console
  2. Click "New Project"
  3. Enter a project name (e.g., k8s-cluster)
  4. Select your preferred location
  5. Click "Create Project"

2.2 Generate API Token

  1. In your Hetzner project, navigate to Security โ†’ API Tokens
  2. Click "Generate API Token"
  3. Enter a description (e.g., terraform-access)
  4. Select Read & Write permissions
  5. Click "Generate API Token"
  6. โš ๏ธ IMPORTANT: Copy the token immediately - it won't be shown again!

๐Ÿ” Step 3: Configure Terraform Variables

3.1 Bootstrap Configuration (1-bootstrap)

Create or update the terraform.tfvars file in the 1-bootstrap directory:

cd terraform/environments/hetzner-mgmt-cluster/1-bootstrap

Create terraform.tfvars:

# Hetzner Cloud API Token
hcloud_token = "YOUR_HETZNER_TOKEN_HERE"

Required Variables for 1-bootstrap:

  • hcloud_token - Your Hetzner Cloud API token (sensitive)

3.2 Components Configuration (2-components)

The 2-components directory already has a terraform.tfvars file. Update it with your GitHub information:

cd ../2-components

Update terraform.tfvars:

# Path to kubeconfig from 1-bootstrap
kubeconfig_path = "../1-bootstrap/kubeconfig"

# GitHub repository information
github_owner      = "YOUR_GITHUB_USERNAME"
github_repository = "bootstrap-cluster"
github_branch     = "main"

# Path within repository where cluster configs are stored
github_path = "./clusters/hetzner-mgmt"

# Flux installation namespace
flux_namespace = "flux-system"

# GitHub Personal Access Token (for private repositories)
# Leave empty for public repositories
github_token = "YOUR_GITHUB_TOKEN_HERE"

Required Variables for 2-components:

  • kubeconfig_path - Path to kubeconfig file (default: ../1-bootstrap/kubeconfig)
  • flux_namespace - Kubernetes namespace for Flux (default: flux-system)
  • github_owner - Your GitHub username/organization (required)
  • github_repository - Repository name (default: bootstrap-cluster)
  • github_branch - Git branch to sync (default: main)
  • github_path - Path in repo for cluster configs (default: ./clusters/hetzner-mgmt)
  • github_token - GitHub Personal Access Token (required for private repos)

๐ŸŽฏ Step 4: GitHub Personal Access Token Setup

4.1 Generate GitHub Token

  1. Go to GitHub Settings โ†’ Developer settings โ†’ Personal access tokens
  2. Click "Generate new token (classic)"
  3. Enter a note (e.g., flux-gitops-access)
  4. Select scopes:
  5. repo (Full control of private repositories)
  6. read:org (Read org and team membership)
  7. Click "Generate token"
  8. โš ๏ธ IMPORTANT: Copy the token immediately!

4.2 Update terraform.tfvars

Add your GitHub token to the 2-components/terraform.tfvars file:

github_token = "ghp_your_token_here"

๐Ÿš€ Step 5: Deploy the Cluster

5.1 Bootstrap Infrastructure

cd terraform/environments/hetzner-mgmt-cluster/1-bootstrap

# Initialize Terraform
terraform init

# Plan the deployment
terraform plan

# Apply the configuration
terraform apply

This will create:

  • Hetzner Cloud servers for the Talos cluster
  • Network configuration
  • Floating IP for MetalLB
  • Generate kubeconfig file

5.2 Install Components

cd ../2-components

# Initialize Terraform
terraform init

# Plan the deployment
terraform plan

# Apply the configuration
terraform apply

This will install:

  • Flux GitOps controller
  • Configure GitOps sync with your repository
  • Create cluster configuration ConfigMap in flux-system namespace
  • MetalLB via Helm chart with automatic floating IP configuration via Flux ConfigMap substitution

โœ… Step 6: Verify Installation

6.1 Check Cluster Access

# Use the generated kubeconfig
export KUBECONFIG=$(pwd)/../1-bootstrap/kubeconfig

# Check cluster nodes
kubectl get nodes

# Check Flux installation
kubectl get pods -n flux-system

# Check cluster configuration ConfigMap
kubectl get configmap cluster-config -n flux-system -o yaml

6.2 Monitor Flux Sync

# Check Flux status
kubectl get gitrepository -n flux-system

# Check kustomizations
kubectl get kustomization -n flux-system

# View Flux logs
kubectl logs -n flux-system -l app=source-controller

# Check Helm releases
kubectl get helmrelease -n metallb-system

# Check MetalLB IP pool configuration
kubectl get ipaddresspool -n metallb-system ip-address-pool -o yaml

# Verify ConfigMap substitution worked
kubectl describe kustomization metallb-install -n flux-system

# Check MetalLB pods
kubectl get pods -n metallb-system

๐Ÿ”ง Troubleshooting

Common Issues

  1. Hetzner API Token Issues

  2. Verify token has Read & Write permissions

  3. Check token isn't expired
  4. Ensure correct project is selected

  5. GitHub Token Issues

  6. Verify token has repo scope

  7. For organization repos, ensure token has org access
  8. Check repository exists and is accessible

  9. Kubeconfig Issues

  10. Verify kubeconfig was generated in 1-bootstrap directory

  11. Check file permissions on kubeconfig
  12. Ensure cluster is fully bootstrapped before running 2-components

  13. Talos Snapshot Issues

  14. Verify snapshot exists in your Hetzner project

  15. Check snapshot ID is correct in terraform files
  16. Ensure snapshot is in the same location as your servers

  17. Talos Configuration Issues

  18. The talos-cluster-patch.yaml is a template that gets processed by terraform

  19. If you see ${primary_ip} in the file, that's normal - terraform replaces it during deployment
  20. The actual configuration is written to talos-cluster-patch-dynamic.yaml (which is ignored by git)

  21. Flux Resource Creation

  22. Flux resources (GitRepository, Kustomization) are created using kubectl apply
  23. This avoids CRD validation issues during terraform planning
  24. Resources are properly cleaned up when running terraform destroy

Making Snapshot ID Configurable (Optional Improvement)

To avoid hardcoding the snapshot ID, you can add it as a terraform variable:

  1. Add this variable to main.tf:
variable "talos_image_id" {
  description = "Hetzner Cloud snapshot ID for Talos OS"
  type        = string
  default     = "336987024"
}
  1. Replace hardcoded image IDs with:
image = var.talos_image_id
  1. Add to terraform.tfvars:
talos_image_id = "YOUR_SNAPSHOT_ID"

Debug Commands

# Check Terraform state
terraform show

# View detailed logs
terraform apply -debug

# Validate configuration
terraform validate

๐Ÿงน Cleanup

To destroy the infrastructure:

# Destroy components first
cd terraform/environments/hetzner-mgmt-cluster/2-components
terraform destroy

# Then destroy bootstrap infrastructure
cd ../1-bootstrap
terraform destroy

๐Ÿ“š Next Steps

After successful deployment:

  1. Configure your applications in clusters/hetzner-mgmt/applications/
  2. Set up monitoring and logging
  3. Configure backup solutions
  4. Review security settings

For more information, see the Hetzner MetalLB Networking Guide.