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: cx33
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: cx33
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: cx33
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: cpx31
image: ubuntu-24.04
location: fsn1
ssh-key: [0]
network: 0
label: lok8s.dev/cluster=production,lok8s.dev/role=worker
- name: worker-2
type: cpx31
image: ubuntu-24.04
location: fsn1
ssh-key: [0]
network: 0
label: lok8s.dev/cluster=production,lok8s.dev/role=workerProvision
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 |