Production-ready Kubernetes cluster on Hetzner Cloud, fully managed with Terraform. Designed for small startups and indie projects that need a cost-effective, secure, and automated infrastructure.
A single terraform apply provisions:
- Talos Linux cluster (immutable, minimal, no SSH) on Hetzner Cloud
- Cilium CNI with eBPF (replaces kube-proxy), Hubble observability
- Traefik ingress controller with Gateway API support
- cert-manager with Let's Encrypt for automatic TLS
- Hetzner CCM + CSI for cloud load balancers and persistent volumes
- VictoriaMetrics + Grafana for metrics monitoring
- Loki + Promtail for centralized log aggregation
- GitHub Actions Runner Controller (ARC) for in-cluster CI/CD runners
- Hetzner DNS management for your domain
- Werf-based application deployment pipeline
Internet
|
[ Hetzner LB (Traefik) ]
|
+--------------+--------------+
| | |
[ cp-1 ] [ cp-2 ] [ cp-3 ] (Talos Linux, control plane)
| | |
+----- Private Network -------+
|
[ Hetzner LB (K8s API) ]
|
You / CI/CD
| Resource | Type | ~Cost/mo |
|---|---|---|
| 3x Control Plane | cx33 | ~34.47 EUR |
| 1x API Load Balancer | lb11 | ~5.39 EUR |
| 1x Traefik Load Balancer | lb11 | ~5.39 EUR |
| Object Storage (state + logs) | S3 | ~1-3 EUR |
| Total | ~46-48 EUR |
Workers are optional. By default, workloads run on control plane nodes.
- Terraform >= 1.7
- kubectl
- talosctl
- A Hetzner Cloud account
See docs/HETZNER_SETUP.md for detailed instructions:
- Create an API token
- Create an S3 bucket for Terraform state
- Upload a Talos Linux snapshot
cd terraform
# Copy and edit the example files
cp terraform.tfvars.example terraform.tfvars
cp backend.tfvars.example backend.tfvars
# Edit terraform.tfvars with your values
# Edit backend.tfvars with your S3 credentials
# Edit backend.tf to set your bucket name# Initialize Terraform (downloads providers, connects to backend)
terraform init
# Review the execution plan
terraform plan
# Apply (~10 minutes for initial deployment)
terraform apply# Save kubeconfig
terraform output -raw kubeconfig > ../kubeconfig
export KUBECONFIG=../kubeconfig
# Verify
kubectl get nodes
kubectl get pods -ASee docs/BOOTSTRAP.md for the full step-by-step guide.
.
├── terraform/ # Infrastructure as Code
│ ├── variables.tf # All input variables
│ ├── versions.tf # Provider versions and configuration
│ ├── backend.tf # S3 remote state configuration
│ ├── network.tf # Hetzner private network
│ ├── firewall.tf # Firewall rules
│ ├── load_balancer.tf # K8s API load balancer
│ ├── servers.tf # Hetzner servers (control plane + workers)
│ ├── talos.tf # Talos machine configs, bootstrap, kubeconfig
│ ├── talos_patches.tf # Talos machine config patches
│ ├── cilium.tf # Cilium CNI (inline manifest for Talos)
│ ├── hcloud_ccm.tf # Hetzner Cloud Controller Manager
│ ├── csr_approver.tf # Kubelet CSR auto-approver
│ ├── gateway_api.tf # Gateway API CRDs + kubeconfig
│ ├── traefik.tf # Traefik ingress controller
│ ├── cert_manager.tf # cert-manager, ClusterIssuer, Gateway, Certificate
│ ├── dns.tf # Hetzner DNS zone and records
│ ├── monitoring.tf # VictoriaMetrics, Grafana, Loki, Promtail, CSI
│ ├── arc.tf # GitHub Actions Runner Controller
│ ├── outputs.tf # Terraform outputs
│ ├── locals.tf # Local computed values
│ ├── terraform.tfvars.example
│ ├── backend.tfvars.example
│ └── charts/ # Local Helm charts
│ └── hcloud-secret/ # Hetzner Cloud token secret
├── charts/ # Shared Helm library charts
│ └── app-template/ # Reusable Deployment+Service+Secret templates
├── apps/ # Application deployments (Werf + Helm)
│ └── myapp/ # Example app (Nginx static site)
│ ├── Dockerfile
│ ├── werf.yaml
│ └── .helm/ # Helm chart (uses app-template)
├── .github/workflows/ # CI/CD pipeline examples
│ ├── infra.yaml.example # Terraform plan/apply workflow
│ └── deploy.yaml.example # Werf deploy workflow
├── docs/ # Documentation
│ ├── HETZNER_SETUP.md # Hetzner Cloud preparation guide
│ ├── BOOTSTRAP.md # Step-by-step bootstrap guide
│ └── APPLICATIONS.md # Werf deployment & app-template reference
├── Makefile # Convenience commands
├── SECURITY.md # Security policy and considerations
└── LICENSE # Apache 2.0
Applications are built and deployed with Werf -- a CLI tool that handles Docker build, push to GHCR, and Helm deploy in one step. Each app lives in apps/<name>/ with a Dockerfile and Helm chart.
The included myapp is a minimal example: an Nginx container serving a static HTML page. It demonstrates the full pattern:
apps/myapp/
├── Dockerfile # Build instructions (nginx + index.html)
├── werf.yaml # Tells Werf which Dockerfile to build
├── index.html # Your application code
└── .helm/
├── Chart.yaml # Depends on shared app-template library chart
├── templates/
│ └── app.yaml # {{ include "app-template.all" . }}
└── values.yaml # Replicas, ports, resources, env, secrets
The shared charts/app-template/ library chart generates Deployment + Service + Secret from values.yaml -- no boilerplate YAML needed.
# Create a new app from the example
make new-app NAME=my-service
# Edit apps/my-service/Dockerfile and .helm/values.yaml
# Push to main branch -- the deploy workflow handles the restSee docs/APPLICATIONS.md for the full guide: values reference, advanced examples, local development with Werf, and CI/CD setup.
# In terraform.tfvars
worker_count = 2
worker_server_type = "cx33"See Hetzner server types for available options:
cx22-- 2 vCPU, 4 GB RAM (~4.49 EUR/mo)cx33-- 3 vCPU, 8 GB RAM (~11.49 EUR/mo)cx43-- 4 vCPU, 16 GB RAM (~18.49 EUR/mo)
Components are modular Terraform files. To disable a component, remove or rename the file:
# Disable ARC (GitHub Actions runners)
mv terraform/arc.tf terraform/arc.tf.disabled
# Disable monitoring stack
mv terraform/monitoring.tf terraform/monitoring.tf.disabledcd terraform
terraform destroyWarning: This will permanently delete ALL cluster resources including volumes and data.
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes
- Open a pull request
This project is licensed under the Apache License 2.0 -- see the LICENSE file for details.