Skip to content

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

  • A CAPI management cluster, or let lo bootstrap a local kind one for you by setting managementCluster.local: true in the spec (see the lok8s CAPI guide). Either way managementCluster.domain is required: it names the management cluster, and local: true only 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:

yaml
# 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:

  1. Generates CAPI Cluster and MachineDeployment manifests
  2. Applies them to the management cluster
  3. CAPH creates Hetzner Cloud servers
  4. Kubeadm bootstraps Kubernetes on the nodes
  5. The workload kubeconfig is written to .kubeconfig/capi-cluster.yaml in your project, named after metadata.name. The management cluster’s own kubeconfig is .kubeconfig/mgmt.example.com.yaml, named after managementCluster.domain. Your ~/.kube/config is never touched.

Provision

bash
lo provision

The 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 infrastructure
  • KubeadmControlPlane: control plane machines
  • MachineDeployment: worker node pools
  • HetznerMachineTemplate: 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:

bash
# 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 nodes

When to use CAPI vs KubeOne

AspectKubeOneCAPI
Management clusterNot requiredRequired
Declarative lifecyclePartialFull
Multi-clusterManualNative
ComplexityLowerHigher

Use KubeOne for single clusters with simple requirements. Use CAPI for multi-cluster environments or GitOps-driven infrastructure.

Next steps


Doc status

AspectDetail
Last reviewed5 September 2026