Cluster API (CAPI) on Hetzner
Provision Kubernetes clusters using Cluster API with the Hetzner infrastructure provider (CAPH).
Single cluster, simple requirements? KubeOne is simpler: see when to use which →
Prerequisites
- lok8s CLI installed
- A CAPI management cluster, or let
lobootstrap a local kind one for you by settingmanagementCluster.local: truein the spec (see the lok8s CAPI guide). Either waymanagementCluster.domainis required: it names the management cluster, andlocal: trueonly decides whether lok8s creates it rather than expecting it to exist already. - clusterctl installed
- Hetzner Cloud API token exported as
HCLOUD_TOKEN
Cluster configuration
Create clusters/example.com/cluster.lok8s.yaml with kind: Capi (the folder is named after your domain), then run lo use example.com. Unlike KubeOne, the Capi driver keeps controlPlane and workers in the spec, because it generates the CAPI machine resources itself; the provider block only carries what CAPH needs:
# clusters/example.com/cluster.lok8s.yaml
apiVersion: cluster.lok8s.dev/v1beta1
kind: Capi
metadata:
name: capi-cluster
spec:
kubernetes:
version: "v1.35.5"
cluster:
# REQUIRED: a domain you control; used for the API endpoint and cert names
domain: example.com
managementCluster:
# REQUIRED for self-hosted CAPI: `lo provision` refuses to start without it
domain: mgmt.example.com
# Bootstrap it as a local kind cluster instead of expecting one to exist
local: true
provider:
name: hetzner
config:
# fsn1, nbg1, or hel1
region: fsn1
# an SSH key that exists in your Hetzner project
sshKeyName: my-key
# stock image; Kubernetes is installed via cloud-init
image: ubuntu-24.04
network:
# private hcloud network
enabled: true
# spread control plane + workers across hosts
placementGroups: true
credentials:
envVars:
- HCLOUD_TOKEN
secretRef: capi-cluster-credentials
controlPlane:
# odd number for etcd quorum
replicas: 3
type: cx33
workers:
platform:
replicas: 2
type: cpx31
bootstrap:
- cilium
- ccm: {networking: {enabled: true}}How CAPI provisioning works
When you run lo provision with kind: Capi, lok8s:
- Generates CAPI Cluster and MachineDeployment manifests
- Applies them to the management cluster
- CAPH creates Hetzner Cloud servers
- Kubeadm bootstraps Kubernetes on the nodes
- The workload kubeconfig is written to
.kubeconfig/capi-cluster.yamlin your project, named aftermetadata.name. The management cluster’s own kubeconfig is.kubeconfig/mgmt.example.com.yaml, named aftermanagementCluster.domain. Your~/.kube/configis never touched.
Provision
lo provisionThe config above asks Hetzner for five servers (3 control plane and 2 workers), billed hourly at Hetzner’s published prices for those server types. Nothing here is one-way: lo destroy removes every server lok8s created, whenever you want.
Template structure
lok8s generates these CAPI resources from your config:
Cluster: cluster-level settings (network, region)HetznerCluster: Hetzner-specific infrastructureKubeadmControlPlane: control plane machinesMachineDeployment: worker node poolsHetznerMachineTemplate: server type and image
Verify
CAPI resources live on the management cluster; your nodes live on the workload cluster. Neither is your default kubectl context, so name each kubeconfig explicitly:
# CAPI cluster status — on the management cluster
kubectl --kubeconfig=.kubeconfig/mgmt.example.com.yaml get clusters -A
# Workload cluster nodes
kubectl --kubeconfig=.kubeconfig/capi-cluster.yaml get nodesWhen to use CAPI vs KubeOne
| Aspect | KubeOne | CAPI |
|---|---|---|
| Management cluster | Not required | Required |
| Declarative lifecycle | Partial | Full |
| Multi-cluster | Manual | Native |
| Complexity | Lower | Higher |
Use KubeOne for single clusters with simple requirements. Use CAPI for multi-cluster environments or GitOps-driven infrastructure.
Next steps
- GitHub Actions: automate CAPI provisioning in CI
- Registration: register with the kubehz dashboard
- KubeOne: simpler alternative provisioner
Doc status
| Aspect | Detail |
|---|---|
| Last reviewed | 5 September 2026 |