Hetzner Provider
The Hetzner provider creates Kubernetes cluster nodes as Hetzner Cloud servers running Talos Linux. It provisions real cloud infrastructure with public IPs, private networking, load balancers, and persistent volumes â ideal for production-grade clusters and cloud testing.
When to Use the Hetzner Provider
Section titled âWhen to Use the Hetzner ProviderâThe Hetzner provider is ideal when you:
- Need production-grade Kubernetes on affordable European cloud infrastructure
- Want real cloud load balancers and persistent volumes (not emulated locally)
- Run performance or integration tests that require dedicated server resources
- Deploy Talos Linux clusters with high-availability placement groups
Prerequisites
Section titled âPrerequisitesâ- Hetzner Cloud account â Sign up at hetzner.com/cloud
- API token â Create one in the Hetzner Cloud Console â Project â Security â API Tokens (read/write permissions)
- Talos ISO or Schematic â Either a Talos Linux ISO must be available in your Hetzner Cloud project (x86 default:
122630, ARM:122629), or a Talos factory schematic ID can be provided viaspec.cluster.talos.schematicIdso KSail builds the snapshot automatically. See Talos options. - Docker â Required locally for
ksailCLI operations
Configuration
Section titled âConfigurationâEnvironment Variable
Section titled âEnvironment VariableâExport the Hetzner Cloud API token so KSail can manage servers:
export HCLOUD_TOKEN=your-hetzner-api-tokenBy default, KSail reads from HCLOUD_TOKEN. To use a different environment variable name, set spec.provider.hetzner.tokenEnvVar in ksail.yaml.
ksail.yaml Reference
Section titled âksail.yaml ReferenceâThe Hetzner provider is configured through spec.provider.hetzner in ksail.yaml:
apiVersion: ksail.io/v1alpha1kind: Clustermetadata: name: my-hetzner-clusterspec: cluster: distribution: Talos provider: Hetzner controlPlanes: 1 workers: 2 talos: # ISO image ID for Hetzner (default: 122630 for x86, use 122629 for ARM) # Ignored when schematicId is set â KSail builds a snapshot instead. iso: 122630 # Optional: Talos factory schematic ID. When set, KSail builds a Hetzner # snapshot from the Talos factory URL and uses it instead of the ISO. # Obtain a schematic ID from https://factory.talos.dev. # schematicId: "" provider: hetzner: # Server types (default: cx23 for both) controlPlaneServerType: "cx23" workerServerType: "cx23" # Datacenter location (default: fsn1) location: "fsn1" # Private network settings networkCidr: "10.0.0.0/16" # networkName: "my-network" # defaults to <cluster>-network # Optional: SSH key for server access (Talos API is primary) # sshKeyName: "my-key" # Optional: override the default env var name (default: HCLOUD_TOKEN) # tokenEnvVar: "MY_CUSTOM_HCLOUD_TOKEN" # Placement group settings for HA placementGroupStrategy: "Spread" # or "None" # placementGroup: "my-placement" # defaults to <cluster>-placement # fallbackLocations: ["nbg1", "hel1"] # placementGroupFallbackToNone: false # Talos OS-level ingress firewall (default: Enabled â provides defense-in-depth) # ingressFirewall: "Enabled" # or "Disabled" # Maximum Hetzner servers for this cluster (default: 10) # serverLimit: 10| Field | Type | Default | Description |
|---|---|---|---|
hetzner.controlPlaneServerType | string | cx23 | Hetzner server type for control-plane nodes |
hetzner.workerServerType | string | cx23 | Hetzner server type for worker nodes |
hetzner.location | string | fsn1 | Datacenter location (fsn1, nbg1, hel1) |
hetzner.networkName | string | <cluster>-network | Private network name |
hetzner.networkCidr | string | 10.0.0.0/16 | Network CIDR block |
hetzner.sshKeyName | string | (empty) | SSH key for server access (optional) |
hetzner.tokenEnvVar | string | HCLOUD_TOKEN | Environment variable containing the API token |
hetzner.placementGroupStrategy | string | Spread | Spread (HA) or None (no placement group) |
hetzner.placementGroup | string | <cluster>-placement | Placement group name |
hetzner.fallbackLocations | list | ["nbg1", "hel1"] | Alternative locations if primary is unavailable |
hetzner.placementGroupFallbackToNone | bool | false | Fall back to no placement group on capacity issues |
hetzner.ingressFirewall | string | Enabled | Talos OS-level ingress firewall (Enabled or Disabled). Generates NetworkDefaultActionConfig + NetworkRuleConfig patches as defense-in-depth alongside the Hetzner Cloud Firewall. |
hetzner.serverLimit | int | 10 | Maximum total Hetzner servers for this cluster. Validated only when Hetzner node autoscaling is enabled â checks control-planes + workers + effective autoscaler capacity, where effective autoscaler capacity is sum(pool.max) or, when autoscaler.node.maxNodesTotal > 0, min(sum(pool.max), autoscaler.node.maxNodesTotal). Set to 0 to use the default of 10. |
Talos options for Hetzner clusters
Section titled âTalos options for Hetzner clustersâ| Field | Type | Default | Description |
|---|---|---|---|
talos.iso | int | 122630 | Hetzner Cloud ISO/image ID for Talos (use 122629 for ARM). Ignored when schematicId is set. |
talos.schematicId | string | (empty) | Talos factory schematic ID. When set, KSail automatically builds and manages a Hetzner snapshot image instead of booting from the ISO. spec.cluster.talos.version must also be set. Not supported for ARM64 server types (cax*). The snapshot is deleted when ksail cluster delete --delete-storage is run. |
Autoscaler Configuration
Section titled âAutoscaler ConfigurationâKSail supports node and pod autoscaling for Hetzner clusters via spec.cluster.autoscaler. Node pools are Hetzner-specific and map directly to the Kubernetes Cluster Autoscaler node group format.
spec: cluster: autoscaler: node: enabled: true expander: LeastWaste # Price | LeastWaste | LeastNodes | Random maxNodesTotal: 20 # 0 = no global cap (sum of pool maxes applies) scaleDownUnneededTime: 10m pools: - name: workers-fsn1 serverType: cx23 location: fsn1 min: 1 max: 5 - name: workers-nbg1 serverType: cx33 location: nbg1 min: 0 max: 3 pod: horizontal: Disabled # Enabled | Disabled (pod autoscaler setting; metrics-server is configured separately) vertical: Disabled # Enabled | Disabled (reserved for future VPA support; not yet implemented) provider: hetzner: serverLimit: 20 # Must be â„ controlPlanes + workers + effective pool capacity (capped by maxNodesTotal when set)| Field | Type | Default | Description |
|---|---|---|---|
autoscaler.node.enabled | bool | false | true â defer node scaling to an external autoscaler; false â KSail manages node counts directly |
autoscaler.node.expander | string | LeastWaste | Expander strategy: Price, LeastWaste, LeastNodes, Random |
autoscaler.node.maxNodesTotal | int | 0 | Maximum total nodes across all pools. 0 disables the global cap. |
autoscaler.node.scaleDownUnneededTime | string | 10m | How long a node must be unneeded before it is eligible for scale-down (e.g. 10m) |
autoscaler.node.pools[].name | string | â | Unique pool name (DNS-1123 label, max 63 chars) |
autoscaler.node.pools[].serverType | string | â | Hetzner server type for this pool (e.g. cx23, cax11) |
autoscaler.node.pools[].location | string | â | Hetzner datacenter location for this pool (e.g. fsn1) |
autoscaler.node.pools[].min | int | â | Minimum nodes in this pool |
autoscaler.node.pools[].max | int | â | Maximum nodes in this pool |
autoscaler.pod.horizontal | string | Disabled | Enabled activates HPA support. Metrics-server is a prerequisite â configure it separately via spec.cluster.metricsServer. |
autoscaler.pod.vertical | string | Disabled | Reserved for future VPA support; not yet implemented. |
Quick Start
Section titled âQuick StartâStep 1: Configure API Token
Section titled âStep 1: Configure API TokenâSet HCLOUD_TOKEN as described in the Configuration section above, then verify it is exported in your shell.
Step 2: Initialize Project
Section titled âStep 2: Initialize Projectâksail cluster init \ --name my-hetzner-cluster \ --distribution Talos \ --provider Hetzner \ --control-planes 1 \ --workers 2This creates ksail.yaml and a talos/ directory for Talos configuration patches.
Step 3: Create Cluster
Section titled âStep 3: Create Clusterâksail cluster createKSail creates Hetzner Cloud servers, boots them with Talos Linux, bootstraps Kubernetes, configures kubectl context, and installs the Hetzner Cloud Controller Manager and CSI driver.
Step 4: Verify Cluster
Section titled âStep 4: Verify Clusterâksail cluster infokubectl get nodes -o widekubectl get pods -n kube-systemStep 5: Cleanup
Section titled âStep 5: Cleanupâksail cluster deleteArchitecture
Section titled âArchitectureâKSail provisions Hetzner Cloud servers running Talos Linux, connected via a private network. The Hetzner Cloud Controller Manager provides native load balancer integration, and the Hetzner CSI Driver provisions persistent volumes backed by Hetzner Block Storage.
graph TB
subgraph "Your Machine"
KSAIL["ksail CLI"]
end
subgraph "Hetzner Cloud"
API["Hetzner Cloud API"]
NET["Private Network"]
LB["Cloud Load Balancer"]
subgraph "Servers"
CP["Control Plane (Talos)"]
W1["Worker Node 1 (Talos)"]
W2["Worker Node 2 (Talos)"]
end
end
KSAIL -->|"HCLOUD_TOKEN"| API
API --> CP
API --> W1
API --> W2
CP --- NET
W1 --- NET
W2 --- NET
LB -->|"traffic"| W1
LB -->|"traffic"| W2
CP -.->|"kubeconfig"| KSAIL
Installed Components
Section titled âInstalled ComponentsâWhen using the Hetzner provider, KSail automatically installs:
- Hetzner Cloud Controller Manager â Provisions Hetzner Cloud Load Balancers for
type: LoadBalancerservices - Hetzner CSI Driver â Provisions Hetzner Block Storage volumes for
PersistentVolumeClaimresources - Placement groups â Distributes servers across physical hosts for high availability (configurable)
- Cluster Autoscaler â Installed automatically when
autoscaler.node.enabled: true(or the deprecatednodeAutoscaling: Enabled). Scales node pools defined underautoscaler.node.poolsbased on pending pod demand.
Firewall Layers
Section titled âFirewall LayersâKSail applies two independent firewall layers for Hetzner Talos clusters:
- Hetzner Cloud Firewall â Restricts external traffic at the network perimeter. KSail manages three rules: Talos API (50000/tcp), Kubernetes API (6443/tcp), and ICMP. Ports for cluster-internal communication (etcd, kubelet, trustd) are not exposed because nodes communicate over the private Hetzner Cloud Network.
- Talos OS-level ingress firewall â Defense-in-depth at the node level, enabled by default (
spec.provider.hetzner.ingressFirewall: Enabled). Blocks all ingress by default; opens only the ports required for each node role (control plane vs worker), restricted to the cluster subnet where appropriate. Disable withingressFirewall: Disabledif you manage node-level firewall rules yourself.
Operations
Section titled âOperationsâTalos Snapshot Lifecycle
Section titled âTalos Snapshot LifecycleâWhen spec.cluster.talos.schematicId is set, KSail manages a Hetzner Cloud snapshot image (instead of booting from the ISO) with the following lifecycle:
- Create â
ksail cluster createcalls the Talos factory to download the raw disk image for the specified schematic and Talos version, uploads it to Hetzner Cloud as a snapshot, and labels it withksail.io/talos-version,ksail.io/talos-schematic, andksail.io/cluster. If a matching snapshot already exists, it is reused. - Reuse â Subsequent
ksail cluster createcalls find the existing snapshot by label and skip the upload step. - Delete â
ksail cluster delete --delete-storageremoves the snapshot along with the cluster. Without--delete-storage, the snapshot is retained (useful when recreating the cluster).
List Clusters
Section titled âList Clustersâksail cluster listCluster Info
Section titled âCluster Infoâksail cluster infoDisplays Kubernetes control-plane and core service endpoints for the current context.
Update Cluster
Section titled âUpdate Clusterâksail cluster updateApplies in-place changes to components (CNI, GitOps engine, cert-manager, etc.). Node scaling changes for Talos are applied in-place. Changes to hetzner.location, hetzner.controlPlaneServerType, or hetzner.networkCidr require cluster recreation. See the Update Behavior table for details.
Deploy with LoadBalancer
Section titled âDeploy with LoadBalancerâkubectl create deployment web --image=nginx --replicas=3kubectl expose deployment web --port=80 --type=LoadBalancerkubectl get svc web --watchThe type: LoadBalancer service provisions a real Hetzner Cloud Load Balancer with a public IP.
Troubleshooting
Section titled âTroubleshootingâhcloud token is not set â The HCLOUD_TOKEN environment variable is missing or empty. Run echo $HCLOUD_TOKEN to check. Re-export if needed. If you use a custom variable name, verify it matches spec.provider.hetzner.tokenEnvVar in ksail.yaml.
Server creation fails with resource unavailability â The selected location may be out of capacity for the requested server type. Configure spec.provider.hetzner.fallbackLocations to try alternative datacenter locations automatically:
spec: provider: hetzner: location: "fsn1" fallbackLocations: ["nbg1", "hel1"]Placement group errors â Hetzner limits spread placement groups to 10 servers per datacenter. Reduce your node count or set placementGroupStrategy: "None". For best-effort HA, set placementGroupFallbackToNone: true to fall back automatically when spread placement fails.
autoscaler configuration exceeds Hetzner server limit â The total node capacity (controlPlanes + workers + effectivePoolCapacity) exceeds hetzner.serverLimit. Increase serverLimit or reduce your pool max values (or lower maxNodesTotal to cap the effective pool capacity).
cloud provider requires an external registry â Hetzner Cloud servers cannot reach Docker-based local registries running on your machine. When spec.cluster.localRegistry is enabled, it must point to an internet-accessible registry (e.g., ghcr.io/myorg). KSail returns this error early if a non-external registry is configured.
context deadline exceeded or connection errors â Verify HCLOUD_TOKEN is valid and has read/write permissions. Check connectivity with curl -sI https://api.hetzner.cloud/v1/servers -H "Authorization: Bearer $HCLOUD_TOKEN".
Cluster deletion leaves orphaned resources â If ksail cluster delete is interrupted, manually clean up in the Hetzner Cloud Console: delete servers, load balancers, networks, and placement groups associated with your cluster name.