The tool needs a basic configuration file, written in YAML format, to handle tasks like creating, upgrading, or deleting clusters. Below is an example where commented lines indicate optional settings:
---
hetzner_token: <your token>
cluster_name: test
kubeconfig_path: "./kubeconfig"
k3s_version: v1.30.3+k3s1
networking:
ssh:
port: 22
use_agent: false # set to true if your key has a passphrase
public_key_path: "~/.ssh/id_ed25519.pub"
private_key_path: "~/.ssh/id_ed25519"
allowed_networks:
ssh:
- 0.0.0.0/0
api: # this will firewall port 6443 on the nodes
- 0.0.0.0/0
public_network:
ipv4: true
ipv6: true
private_network:
enabled: true
subnet: 10.0.0.0/16
existing_network_name: ""
cni:
enabled: true
encryption: false
mode: flannel
# cluster_cidr: 10.244.0.0/16 # optional: a custom IPv4/IPv6 network CIDR to use for pod IPs
# service_cidr: 10.43.0.0/16 # optional: a custom IPv4/IPv6 network CIDR to use for service IPs. Warning, if you change this, you should also change cluster_dns!
# cluster_dns: 10.43.0.10 # optional: IPv4 Cluster IP for coredns service. Needs to be an address from the service_cidr range
# manifests:
# cloud_controller_manager_manifest_url: "https://github.com/hetznercloud/hcloud-cloud-controller-manager/releases/download/v1.23.0/ccm-networks.yaml"
# csi_driver_manifest_url: "https://raw.githubusercontent.com/hetznercloud/csi-driver/v2.12.0/deploy/kubernetes/hcloud-csi.yml"
# system_upgrade_controller_deployment_manifest_url: "https://github.com/rancher/system-upgrade-controller/releases/download/v0.14.2/system-upgrade-controller.yaml"
# system_upgrade_controller_crd_manifest_url: "https://github.com/rancher/system-upgrade-controller/releases/download/v0.14.2/crd.yaml"
# cluster_autoscaler_manifest_url: "https://raw.githubusercontent.com/kubernetes/autoscaler/master/cluster-autoscaler/cloudprovider/hetzner/examples/cluster-autoscaler-run-on-master.yaml"
# cluster_autoscaler_container_image_tag: "v1.32.0"
datastore:
mode: etcd # etcd (default) or external
external_datastore_endpoint: postgres://....
schedule_workloads_on_masters: false
# image: rocky-9 # optional: default is ubuntu-24.04
# autoscaling_image: 103908130 # optional, defaults to the `image` setting
# snapshot_os: microos # optional: specified the os type when using a custom snapshot
masters_pool:
instance_type: cpx21
instance_count: 3 # for HA; you can also create a single master cluster for dev and testing (not recommended for production)
locations: # You can choose a single location for single master clusters or if you prefer to have all masters in the same location. For regional clusters (which are only available in the eu-central network zone), each master needs to be placed in a separate location.
- fsn1
- hel1
- nbg1
worker_node_pools:
- name: small-static
instance_type: cpx21
instance_count: 4
location: hel1
# image: debian-11
# labels:
# - key: purpose
# value: blah
# taints:
# - key: something
# value: value1:NoSchedule
- name: medium-autoscaled
instance_type: cpx31
location: fsn1
autoscaling:
enabled: true
min_instances: 0
max_instances: 3
embedded_registry_mirror:
enabled: false # Enables fast p2p distribution of container images between nodes for faster pod startup. Check if your k3s version is compatible before enabling this option. You can find more information at https://docs.k3s.io/installation/registry-mirror
protect_against_deletion: true
create_load_balancer_for_the_kubernetes_api: false # Just a heads up: right now, we can’t limit access to the load balancer by IP through the firewall. This feature hasn’t been added by Hetzner yet.
# additional_packages:
# - somepackage
# post_create_commands:
# - apt update
# - apt upgrade -y
# - apt autoremove -y
# kube_api_server_args:
# - arg1
# - ...
# kube_scheduler_args:
# - arg1
# - ...
# kube_controller_manager_args:
# - arg1
# - ...
# kube_cloud_controller_manager_args:
# - arg1
# - ...
# kubelet_args:
# - arg1
# - ...
# kube_proxy_args:
# - arg1
# - ...
# api_server_hostname: k8s.example.com # optional: DNS for the k8s API LoadBalancer. After the script has run, create a DNS record with the address of the API LoadBalancer.
Most settings are straightforward and easy to understand. To see a list of available k3s releases, you can run the command hetzner-k3s releases
.
If you prefer not to include the Hetzner token directly in the config file—perhaps for use with CI or to safely commit the config to a repository—you can use the HCLOUD_TOKEN
environment variable instead. This variable takes precedence over the config file.
When setting masters_pool
.instance_count
, keep in mind that if you set it to 1, the tool will create a control plane that is not highly available. For production clusters, it’s better to set this to a number greater than 1. To avoid split brain issues with etcd, this number should be odd, and 3 is the recommended value. Additionally, for production environments, it’s a good idea to configure masters in different locations using the masters_pool
.locations
setting.
You can define any number of worker node pools, either static or autoscaled, and create pools with nodes of different specifications to handle various workloads.
Hetzner Cloud init settings, such as additional_packages
and post_create_commands
, can be specified at the root level of the configuration file or for each individual pool if different settings are needed. If these settings are configured at the pool level, they will override any settings defined at the root level.
Currently, Hetzner Cloud offers six locations: two in Germany (nbg1
in Nuremberg and fsn1
in Falkenstein), one in Finland (hel1
in Helsinki), two in the USA (ash
in Ashburn, Virginia and hil
in Hillsboro, Oregon), and one in Singapore (sin
). Be aware that not all instance types are available in every location, so it’s a good idea to check the Hetzner site and their status page for details.
To explore the available instance types and their specifications, you can either check them manually when adding an instance within a project or run the following command with your Hetzner token:
curl -H "Authorization: Bearer $API_TOKEN" 'https://api.hetzner.cloud/v1/server_types'
To create the cluster run:
hetzner-k3s create --config cluster_config.yaml | tee create.log
This process will take a few minutes, depending on how many master and worker nodes you have.
To improve security and save on IPv4 address costs, you can disable the public interface for all nodes by setting enable_public_net_ipv4: false
and enable_public_net_ipv6: false
. These settings are global and will apply to all master and worker nodes. If you disable public IPs, make sure to run hetzner-k3s from a machine that has access to the same private network as the nodes, either directly or through a VPN.
Additional networking setup is required via cloud-init, so it’s important that the machine you use to run hetzner-k3s has internet access and DNS configured correctly. Otherwise, the cluster creation process will get stuck after creating the nodes. For more details and instructions, you can refer to this discussion.
By default, the image used for all nodes is ubuntu-24.04
, but you can specify a different default image by using the root-level image
config option. You can also set different images for different static node pools by using the image
config option within each node pool. For example, if you have node pools with ARM instances, you can specify the correct OS image for ARM. To do this, set image
to 103908130
with the specific image ID.
However, for autoscaling, there’s a current limitation in the Cluster Autoscaler for Hetzner. You can’t specify different images for each autoscaled pool yet. For now, if you want to use a different image for all autoscaling pools, you can set the autoscaling_image
option to override the default image
setting.
To see the list of available images, run the following:
export API_TOKEN=...
curl -H "Authorization: Bearer $API_TOKEN" 'https://api.hetzner.cloud/v1/images?per_page=100'
Besides the default OS images, you can also use a snapshot created from an existing instance. When using custom snapshots, make sure to specify the ID of the snapshot or image, not the description you assigned when creating the template instance.
I’ve tested snapshots with openSUSE MicroOS, but other options might work as well. You can easily create a MicroOS snapshot using this Terraform-based tool. The process only takes a few minutes. Once the snapshot is ready, you can use it with hetzner-k3s by setting the image
configuration option to the ID of the snapshot and snapshot_os
to microos
.
If you plan to create multiple clusters within the same project, refer to the section on Configuring Cluster-CIDR and Service-CIDR. Ensure that each cluster has its own unique Cluster-CIDR and Service-CIDR. Overlapping ranges will cause issues. However, I still recommend separating clusters into different projects. This makes it easier to clean up resources—if you want to delete a cluster, simply delete the entire project.
Cluster-CIDR and Service-CIDR define the IP ranges used for pods and services, respectively. In most cases, you won’t need to change these values. However, advanced setups might require adjustments to avoid network conflicts.
Changing the Cluster-CIDR (Pod IP Range):
To modify the Cluster-CIDR, uncomment or add the cluster_cidr
option in your cluster configuration file and specify a valid CIDR notation for the network. Make sure this network is not a subnet of your private network.
Changing the Service-CIDR (Service IP Range):
To adjust the Service-CIDR, uncomment or add the service_cidr
option in your configuration file and provide a valid CIDR notation. Again, ensure this network is not a subnet of your private network. Also, uncomment the cluster_dns
option and provide a single IP address from the service_cidr
range. This sets the IP address for the coredns service.
Sizing the Networks:
The networks you choose should have enough space for your expected number of pods and services. By default, /16
networks are used. Select an appropriate size, as changing the CIDR later is not supported.
The create
command can be run multiple times with the same configuration without causing issues, as the process is idempotent. If the process gets stuck or encounters errors (e.g., due to Hetzner API unavailability or timeouts), you can stop the command and rerun it with the same configuration to continue where it left off. Note that the kubeconfig will be overwritten each time you rerun the command.
- Using a snapshot instead of a default image will take longer to create instances compared to regular images.
- The
networking
.allowed_networks
.api
setting specifies which networks can access the Kubernetes API, but this currently only works with single-master clusters. Multi-master HA clusters can optionally use a load balancer for the API, but Hetzner’s firewalls do not yet support load balancers. - If you enable autoscaling for a nodepool, avoid changing this setting later, as it can cause issues with the autoscaler.
- Autoscaling is only supported with Ubuntu or other default images, not snapshots.
- SSH keys with passphrases can only be used if you set
networking
.ssh
.use_ssh_agent
totrue
and use an SSH agent to access your key. For example, on macOS, you can start an agent like this:
eval "$(ssh-agent -s)"
ssh-add --apple-use-keychain ~/.ssh/<private key>