KubeOne on Hetzner
Set up a production Kubernetes cluster using KubeOne as the provisioner.
Prerequisites
- lok8s CLI installed
- hcloud CLI installed and authenticated
- An SSH key pair added to your Hetzner project
- Hetzner Cloud API token exported as
HCLOUD_TOKEN
Cluster configuration
Create clusters/example.com/cluster.lok8s.yaml with kind: KubeOne (the folder is named after your domain), then run lo use example.com. The machines are declared in the Hetzner provider descriptor: one server entry per node, with the role in the lok8s.dev/role label. See First Cluster for every key.
# clusters/example.com/cluster.lok8s.yaml
apiVersion: cluster.lok8s.dev/v1beta1
kind: KubeOne
metadata:
name: production
spec:
kubernetes:
version: "v1.35.5"
cluster:
# REQUIRED: a domain you control; used for the API endpoint and cert names
domain: example.com
provider:
name: hetzner
config:
cluster_name: production
sshUser: root
sshPrivateKey: ~/.ssh/id_ed25519
sshPublicKey: ~/.ssh/id_ed25519.pub
ssh-key:
- name: production
public-key-from-file: ~/.ssh/id_ed25519.pub
network:
- name: production
ip-range: 10.0.0.0/16
"#subnets":
- network-zone: eu-central
type: cloud
ip-range: 10.0.0.0/24
server:
- name: cp-1
type: cpx32
image: ubuntu-24.04
location: fsn1
ssh-key: [0]
network: 0
label: lok8s.dev/cluster=production,lok8s.dev/role=control-plane
- name: cp-2
type: cpx32
image: ubuntu-24.04
location: fsn1
ssh-key: [0]
network: 0
label: lok8s.dev/cluster=production,lok8s.dev/role=control-plane
- name: cp-3
type: cpx32
image: ubuntu-24.04
location: fsn1
ssh-key: [0]
network: 0
label: lok8s.dev/cluster=production,lok8s.dev/role=control-plane
- name: worker-1
type: cpx32
image: ubuntu-24.04
location: fsn1
ssh-key: [0]
network: 0
label: lok8s.dev/cluster=production,lok8s.dev/role=worker
- name: worker-2
type: cpx32
image: ubuntu-24.04
location: fsn1
ssh-key: [0]
network: 0
label: lok8s.dev/cluster=production,lok8s.dev/role=workerThe example uses cpx32 (4 vCPU, 8 GB RAM) for every server. Any type with at least the same vCPU and RAM that your Hetzner account offers in the location works too.
Stock differs per account and changes by the hour: if Hetzner refuses a type, use another type or another location (fsn1, nbg1 or hel1).
Provision
lo provisionThis will:
- Create Hetzner Cloud servers for control plane and workers
- Set up the private network
- Install Kubernetes via KubeOne
- Write the kubeconfig to
.kubeconfig/production.yamlin your project, named aftermetadata.name, mode0600. Your~/.kube/configis never touched.
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.
Verify
kubectl still points at whatever context it used before, so select the new cluster explicitly; otherwise get nodes answers for your previous cluster:
export KUBECONFIG=.kubeconfig/production.yaml
kubectl get nodes
# (AGE trimmed — VERSION is what to check)
# NAME STATUS ROLES VERSION
# cp-1 Ready control-plane v1.35.5
# cp-2 Ready control-plane v1.35.5
# cp-3 Ready control-plane v1.35.5
# worker-1 Ready <none> v1.35.5
# worker-2 Ready <none> v1.35.5HA considerations
- Declare three
control-planeservers for production - Control plane nodes are spread across fault domains automatically
- The private network (
network[].ip-range) isolates cluster traffic
Adding bootstrap addons
Cluster-infra addons (CNI, CCM, cert-manager, monitoring, …) are listed under spec.bootstrap and applied in order at provision time:
bootstrap:
- cilium
- ccm
- monitoringRun lo bootstrap to apply bootstrap changes to an existing cluster; it re-applies spec.bootstrap and skips the infrastructure reconcile. lo provision does both and is safe to re-run too.
Next steps
- Registration: register with the kubehz dashboard
- GitHub Actions: automate provisioning in CI
- CAPI: alternative provisioner using Cluster API
Doc status
| Aspect | Detail |
|---|---|
| Last reviewed | 5 September 2026 |