Skip to content

Quickstart ​

Get a Kubernetes cluster running on Hetzner in about 15 minutes.

Prerequisites ​

  • A Hetzner Cloud account, and a project API token with Read & Write permission (Hetzner Console → Security → API tokens):

    bash
    export HCLOUD_TOKEN="<your-token>"
  • The lok8s CLI lo, one static Go binary. Download it from a lok8s release, check it against checksums.txt, then install it. Pick the archive for your system (linux or darwin, amd64 or arm64):

    bash
    V=v0.12.0; A=lo-linux-amd64.tar.gz
    curl -fsSLO "https://github.com/kernpilot/lok8s/releases/download/${V}/${A}"
    curl -fsSLO "https://github.com/kernpilot/lok8s/releases/download/${V}/checksums.txt"
    sha256sum --ignore-missing -c checksums.txt   # macOS: shasum -a 256 --ignore-missing -c checksums.txt
    tar -xzf "${A}" lo && mkdir -p "$HOME/.local/bin" && install -m 0755 lo "$HOME/.local/bin/lo"

    Then create your project and install the pinned KubeOne toolchain into its .bin/ folder. Nothing touches your system:

    bash
    mkdir my-project && cd my-project
    lo init project
    lo toolchain install --groups core,local,cloud

    Already using b? These two install lo and the same toolchain:

    bash
    b env add github.com/kernpilot/lok8s#kubeone
    b install
  • An SSH key pair (Ed25519 recommended)

  • A domain you control: the cluster’s API endpoint and certificate names are derived from it. You set it in step 1; the reference config ships example.com as a placeholder, not a default.

1. Create your cluster config ​

lo keeps one folder per cluster, named after the cluster’s domain: clusters/<domain>/cluster.lok8s.yaml. Create the folder inside your project and save the file there:

bash
mkdir -p clusters/example.com
# paste the file below into clusters/example.com/cluster.lok8s.yaml
lo use example.com

lo use marks the domain as active, so every later command knows which cluster you mean.

yaml
# clusters/example.com/cluster.lok8s.yaml
apiVersion: cluster.lok8s.dev/v1beta1
kind: KubeOne
metadata:
  name: my-cluster
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
    # The machines, in Hetzner's own terms: each `server` entry becomes one
    # `hcloud server create`, and its keys are hcloud flags. The role comes
    # from the lok8s.dev/role label.
    config:
      cluster_name: my-cluster
      sshUser: root
      sshPrivateKey: ~/.ssh/id_ed25519
      sshPublicKey: ~/.ssh/id_ed25519.pub
      ssh-key:
        - name: my-cluster
          public-key-from-file: ~/.ssh/id_ed25519.pub
      network:
        - name: my-cluster
          ip-range: 10.0.0.0/16
          "#subnets":
            - network-zone: eu-central
              type: cloud
              ip-range: 10.0.0.0/24
      server:
        # 1 control plane for dev; add two more for HA
        - name: cp-1
          type: cpx32
          image: ubuntu-24.04
          # fsn1, nbg1, or hel1
          location: fsn1
          ssh-key: [0]
          network: 0
          label: lok8s.dev/cluster=my-cluster,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=my-cluster,lok8s.dev/role=worker
        - name: worker-2
          type: cpx32
          image: ubuntu-24.04
          location: fsn1
          ssh-key: [0]
          network: 0
          label: lok8s.dev/cluster=my-cluster,lok8s.dev/role=worker

The 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).

Change domain (and the folder name) to a domain you control before the next step: nothing in this file can guess it for you. The three SSH paths assume a default-named Ed25519 key (~/.ssh/id_ed25519); if yours is named or stored differently, update those too.

2. Provision ​

bash
lo provision

This creates your control plane and worker nodes on Hetzner Cloud. The kubeconfig is written to .kubeconfig/my-cluster.yaml in your project, named after metadata.name. Your ~/.kube/config is never touched.

Provisioning typically takes 10–15 minutes. Hetzner bills the three servers by the hour at its published prices. You can delete everything at any time with lo destroy.

3. Verify ​

kubectl still points at whatever context it used before, so select the new cluster explicitly; otherwise get nodes answers for your previous one:

bash
export KUBECONFIG=.kubeconfig/my-cluster.yaml
kubectl get nodes

You should see your control plane and worker nodes in Ready state.

4. Register with kubehz (optional) ​

This step, and only this step, needs a free kubehz account; the prerequisites above are all the cluster itself requires. Sign-up is open for the first 25 accounts, then by invite (see Dashboard & account).

Connect your cluster to the kubehz dashboard for read-only health visibility. Registration is outbound-only: kubehz never gets access into your cluster.

bash
lo kubehz register

This announces the cluster with the status Creating. Then claim it at app.kubehz.cloud to prove ownership and link it to your account (register → claim → owned). See Registration for the details and what the dashboard shows.

Next steps ​

Doc status ​

AspectDetail
StateLive: matches the shipped CLI flow; provisioning duration is a conservative bound, not yet timed on the quickstart topology
Last reviewed5 September 2026