- Go 98.5%
- Go Template 1.3%
- Dockerfile 0.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
Some checks failed
Cut release / changelog (push) Has been skipped
Cut release / test (push) Failing after 4s
Cut release / generate (push) Failing after 4s
Cut release / controllers (push) Failing after 4s
Cut release / chart (push) Successful in 9s
Roll release / plan (push) Failing after 3s
Roll release / image (push) Has been skipped
Roll release / chart (push) Has been skipped
Roll release / release (push) Has been skipped
Security / govulncheck (push) Successful in 1m47s
Security / govulncheck (shipped toolchain) (push) Successful in 1m48s
Cut release / lint (push) Successful in 3m54s
Cut release / ci (push) Failing after 0s
Cut release / cut (push) Has been skipped
|
||
| .github | ||
| charts/karpenter-provider-hetzner | ||
| cmd/controller | ||
| docs | ||
| examples | ||
| hack | ||
| pkg | ||
| .gitignore | ||
| .golangci.yml | ||
| CHANGELOG.md | ||
| CHANGELOG.upstream.md | ||
| CODE_OF_CONDUCT.md | ||
| CONTRIBUTING.md | ||
| Dockerfile | ||
| go.mod | ||
| go.sum | ||
| LICENSE | ||
| license_test.go | ||
| README.md | ||
| renovate.json | ||
| SECURITY.md | ||
| Taskfile.yaml | ||
karpenter-provider-hetzner (fork)
A Karpenter cloud provider for Hetzner Cloud. It creates Hetzner Cloud servers as Kubernetes nodes for pending pods, choosing the cheapest server type that fits from Hetzner's live pricing, and removes them when they are no longer needed.
This is a fork of stubbi/karpenter-provider-hetzner, the original implementation, maintained independently since its 3.0.1 release. It was forked because contributions there stalled: no pull request was merged between 2026-09-07 and 2026-10-03, and eight of the nine contributed pull requests opened in that time waited 10 to 26 days without a review. With AI-assisted development and a provider this straightforward, carrying changes in a fork costs less than waiting for a maintainer to review them.
Status
The project is pre-1.0. It continues upstream karpenter-provider-hetzner 3.0.1; see the upstream changelog for its history. Release images are multi-arch, signed with cosign, and carry SLSA provenance and an SBOM. Pin a released version in production.
Features
- All Hetzner Cloud server types: CX, CPX, CAX (Arm), and CCX.
- Cheapest-first selection, priced from Hetzner's net hourly price.
- Capacity awareness: an offering Hetzner reports as unavailable in a location is skipped for a few minutes, and Karpenter falls back to another.
- Drift detection on image, network, firewall, and server type, which replaces the node.
- Debian images, Hetzner's own or your labelled snapshots, resolved per architecture.
- Placement groups that spread nodes across physical hosts.
- Optional public IPv4 and IPv6, for clusters on a private network.
- Several clusters per Hetzner project: every server carries its cluster's name, and a controller touches only its own.
How it works
A NodePool references an HCloudNodeClass. When pods are unschedulable,
Karpenter asks this provider for the instance types it can offer, picks the
cheapest compatible offering, and the provider creates the server. The server
boots with a cloud-init document the provider builds from the node class's
bootstrap: your parts around a join that installs k0s, with a join token made
for that node alone.
Install
You need:
- a k0s cluster on Hetzner Cloud, or another cluster whose nodes you join with a join of your own, as About node bootstrap describes;
- the address of its API server, as a new server reaches it before it joins;
- a Hetzner Cloud API token with read and write access.
To install:
-
Store the token in a Secret:
kubectl create secret generic hcloud-token \ --namespace kube-system \ --from-literal=token=<hcloud-token> -
Install the chart, with
<cluster-name>naming this cluster:helm install karpenter-provider-hetzner \ oci://ghcr.io/mikluko/charts/karpenter-provider-hetzner \ --namespace kube-system \ --set clusterName=<cluster-name> \ --set apiServerURL=https://<api-server-address>:6443 \ --set auth.secretRef.name=hcloud-token
clusterName and apiServerURL are required. The controller manages only
servers labelled with clusterName, and new nodes join through apiServerURL.
The chart installs three CRDs from its crds/ directory: HCloudNodeClass, and
the Karpenter NodePool and NodeClaim.
Upgrade
Helm installs the CRDs in crds/ and never updates them. When the release notes
say the CRDs changed, apply them before upgrading the chart:
-
Apply the CRDs of the release, with
<version>naming it, for examplev0.1.0:BASE=https://raw.githubusercontent.com/mikluko/karpenter-provider-hetzner/<version>/charts/karpenter-provider-hetzner/crds kubectl apply --server-side --force-conflicts \ -f $BASE/karpenter.hetzner.cloud_hcloudnodeclasses.yaml \ -f $BASE/karpenter.sh_nodeclaims.yaml \ -f $BASE/karpenter.sh_nodepools.yaml--force-conflictstakes the fields over from Helm, which created the CRDs. -
Upgrade the chart, with
<chart-version>naming the same release without thev, for example0.1.0:helm upgrade karpenter-provider-hetzner \ oci://ghcr.io/mikluko/charts/karpenter-provider-hetzner \ --namespace kube-system --version <chart-version> --reuse-values
If a GitOps tool manages your CRDs, update them there instead; it reverts a manual apply.
Provision nodes
Create an HCloudNodeClass that says how servers are built, and a NodePool
that says what Karpenter may create:
apiVersion: karpenter.hetzner.cloud/v1
kind: HCloudNodeClass
metadata:
name: default
spec:
locations: [nbg1, fsn1]
networkID: 123456
imageSelector:
family: debian
version: "13"
enablePublicIPv4: false
bootstrap:
k0sVersion: v1.34.1+k0s.0
---
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: default
spec:
template:
spec:
nodeClassRef:
group: karpenter.hetzner.cloud
kind: HCloudNodeClass
name: default
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: [on-demand]
- key: kubernetes.io/arch
operator: In
values: [amd64]
- key: karpenter.hetzner.cloud/server-family
operator: In
values: [cx, cpx]
limits:
cpu: "100"
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
k0sVersion names the k0s release the node installs, and should match the
control plane's. Join Debian nodes to a k0s cluster
walks through a node class, and About node bootstrap
covers the parts around the join and how to replace it.
The examples/ directory holds the node class and node pool from
that guide, and a pair of node pools for amd64 and arm64.
Every field, status condition, node label, and controller setting is listed in the reference.
Scheduling and cost
- Hetzner sells no spot capacity, so pin
karpenter.sh/capacity-typetoon-demandin every NodePool. Without it, Karpenter requestsspotfor every consolidation replacement, no offering matches, and the replacement never launches. New nodes and other disruptions still work, which hides the fault. - Selection is cheapest-first. A NodePool that allows several architectures gets
whichever is cheapest for the requested shape, which may be Arm (CAX). Allow
arm64only once your images are multi-arch. - Prices are Hetzner's net figures, before VAT, as on the invoice.
- Hetzner bills a primary IPv4 separately. On a private-network cluster, set
enablePublicIPv4: falseto drop it.
Development
See CONTRIBUTING.md and the Code of Conduct.
Security
To report a vulnerability, follow SECURITY.md. Do not open a public issue.