> ## Documentation Index
> Fetch the complete documentation index at: https://docs.siderolabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Hetzner

> Creating a cluster via the CLI (hcloud) on Hetzner.

export const release_v1_13 = 'v1.13.0';

export const VersionWarningBanner = () => {
  const latestVersion = "v1.13";
  const [latestUrl, setLatestUrl] = useState(null);
  const [currentVersion, setCurrentVersion] = useState(null);
  const [isBeta, setIsBeta] = useState(false);
  const parseVersion = v => v.replace("v", "").split(".").map(Number);
  const isGreaterVersion = (a, b) => {
    const [aMajor, aMinor] = parseVersion(a);
    const [bMajor, bMinor] = parseVersion(b);
    if (aMajor > bMajor) return true;
    if (aMajor === bMajor && aMinor > bMinor) return true;
    return false;
  };
  useEffect(() => {
    if (typeof window === "undefined") return;
    const {pathname, hash, search} = window.location;
    const match = pathname.match(/\/talos\/(v\d+\.\d+)\//);
    if (!match) return;
    const detectedVersion = match[1];
    if (detectedVersion === latestVersion) return;
    setCurrentVersion(detectedVersion);
    if (isGreaterVersion(detectedVersion, latestVersion)) {
      setIsBeta(true);
    }
    const newPath = pathname.replace(`/talos/${detectedVersion}/`, `/talos/${latestVersion}/`);
    setLatestUrl(`${newPath}${search}${hash}`);
  }, []);
  if (!latestUrl || !currentVersion) return null;
  return <div className="not-prose sticky top-6 z-50 my-6">
      <div className="border border-yellow-500/30 bg-yellow-500/10 px-4 py-3 rounded-xl">
        <div className="text-sm">
          {isBeta ? <>
              ⚠️ You are viewing a <strong>beta version</strong> of Talos ({currentVersion}).
              This version may be unstable.
              <a href={latestUrl} className="ml-2 underline text-yellow-400 hover:text-yellow-300 font-medium">
                View latest stable version {latestVersion} →
              </a>
            </> : <>
              ⚠️ You are viewing an older version of Talos ({currentVersion}).
              <a href={latestUrl} className="ml-2 underline text-yellow-400 hover:text-yellow-300 font-medium">
                View the latest version {latestVersion} →
              </a>
            </>}
        </div>
      </div>
    </div>;
};

<VersionWarningBanner />

## Upload image

<Note>
  Hetzner Cloud provides Talos as a Public ISO with the schematic id `ce4c980550dd2ab1b17bbf2b08801c7eb59418eafe8f279833297925d67c7515` (Hetzner + qemu-guest-agent) since 2025-04-23.
  Minor updates of the ISO will be provided by Hetzner Cloud on a best effort basis.

  If you need an ISO with a different schematic id, please email the support team to get a Talos ISO uploaded by following [issues:3599](https://github.com/siderolabs/talos/issues/3599#issuecomment-841172018) or you can prepare the image snapshot by yourself.
</Note>

There are three options to upload your own:

1. Run an instance in rescue mode and replace the system OS with the Talos image
2. Use [Hashicorp Packer](https://www.packer.io/docs/builders/hetzner-cloud) to prepare an image
3. Use special utility [hcloud-upload-image](https://github.com/apricote/hcloud-upload-image/)

### Rescue mode

Create a new Server in the Hetzner console.
Enable the Hetzner Rescue System for this server and reboot.
Upon a reboot, the server will boot a special minimal Linux distribution designed for repair and reinstall.
Once running, login to the server using `ssh` to prepare the system disk by doing the following:

<CodeBlock lang="sh">
  {`
    # Check that you are in Rescue mode
    df

    ### Result is like:
    # udev                   987432         0    987432   0% /dev
    # 213.133.99.101:/nfs 308577696 247015616  45817536  85% /root/.oldroot/nfs
    # overlay                995672      8340    987332   1% /
    # tmpfs                  995672         0    995672   0% /dev/shm
    # tmpfs                  398272       572    397700   1% /run
    # tmpfs                    5120         0      5120   0% /run/lock
    # tmpfs                  199132         0    199132   0% /run/user/0

    # Download the Talos image
    cd /tmp
    wget -O /tmp/talos.raw.xz https://factory.talos.dev/image/376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba/${release_v1_13}/hcloud-amd64.raw.xz
    # Replace system
    xz -d -c /tmp/talos.raw.xz | dd of=/dev/sda && sync
    # shutdown the instance
    shutdown -h now
    `}
</CodeBlock>

To make sure disk content is consistent, it is recommended to shut the server down before taking an image (snapshot).
Once shut down, simply create an image (snapshot) from the console.
You can now use this snapshot to run Talos on the cloud.

### Packer

Install [packer](https://learn.hashicorp.com/tutorials/packer/get-started-install-cli) to the local machine.

Create a config file for packer to use:

<CodeBlock lang="hcl" filename="hcloud.pkr.hcl">
  {`
    packer {
    required_plugins {
      hcloud = {
        source  = "github.com/hetznercloud/hcloud"
        version = "~> 1"
      }
    }
    }

    variable "talos_version" {
    type    = string
    default = "${release_v1_13}"
    }

    variable "arch" {
    type    = string
    default = "amd64"
    }

    variable "server_type" {
    type    = string
    default = "cx22"
    }

    variable "server_location" {
    type    = string
    default = "hel1"
    }

    locals {
    image = "https://factory.talos.dev/image/376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba/\${var.talos_version}/hcloud-\${var.arch}.raw.xz"
    }

    source "hcloud" "talos" {
    rescue       = "linux64"
    image        = "debian-11"
    location     = "\${var.server_location}"
    server_type  = "\${var.server_type}"
    ssh_username = "root"

    snapshot_name   = "talos system disk - \${var.arch} - \${var.talos_version}"
    snapshot_labels = {
      type    = "infra",
      os      = "talos",
      version = "\${var.talos_version}",
      arch    = "\${var.arch}",
    }
    }

    build {
    sources = ["source.hcloud.talos"]

    provisioner "shell" {
      inline = [
        "apt-get install -y wget",
        "wget -O /tmp/talos.raw.xz \${local.image}",
        "xz -d -c /tmp/talos.raw.xz | dd of=/dev/sda && sync",
      ]
    }
    }
    `}
</CodeBlock>

Additionally, you could create a file containing

```hcl theme={null}
arch            = "arm64"
server_type     = "cax11"
server_location = "fsn1"
```

and build the snapshot for arm64.

Create a new image by issuing the commands shown below.
Note that to create a new API token for your Project, switch into the Hetzner Cloud Console, choose a Project, go to Access → Security, and create a new token.

```bash theme={null}
# First you need set the API Token
export HCLOUD_TOKEN=${TOKEN}

# Upload image
packer init .
packer build .
# Save the image ID
export IMAGE_ID=<image-id-in-packer-output>
```

After doing this, you can find the snapshot in the console interface.

### hcloud-upload-image

Install `hcloud-upload-image` as described [in its repository](https://github.com/apricote/hcloud-upload-image/?tab=readme-ov-file#getting-started). You can either download the binary, build it from source or use the Docker container.

For process simplification you can use this `bash` script:

<CodeBlock lang="sh">
  {`
    #!/usr/bin/env bash
    export TALOS_IMAGE_VERSION=${release_v1_13} # You can change to the current version
    export TALOS_IMAGE_ARCH=amd64 # You can change to arm architecture
    export HCLOUD_SERVER_ARCH=x86 # HCloud server architecture can be x86 or arm
    wget https://factory.talos.dev/image/376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba/$TALOS_IMAGE_VERSION/hcloud-$TALOS_IMAGE_ARCH.raw.xz
    hcloud-upload-image upload --image-path hcloud-$TALOS_IMAGE_ARCH.raw.xz --architecture $HCLOUD_SERVER_ARCH --compression xz
    `}
</CodeBlock>

After these actions, you can find the snapshot in the console interface.

## Creating a cluster via the CLI

This section assumes you have the [hcloud console utility](https://community.hetzner.com/tutorials/howto-hcloud-cli) on your local machine.

```bash theme={null}
# Set hcloud context and api key
hcloud context create talos-tutorial
```

### Create a load balancer

Create a load balancer by issuing the commands shown below.
Save the IP/DNS name, as this info will be used in the next step.

```bash theme={null}
hcloud load-balancer create --name controlplane --network-zone eu-central --type lb11 --label 'type=controlplane'

### Result is like:
# LoadBalancer 484487 created
# IPv4: 49.12.X.X
# IPv6: 2a01:4f8:X:X::1

hcloud load-balancer add-service controlplane \
    --listen-port 6443 --destination-port 6443 --protocol tcp
hcloud load-balancer add-target controlplane \
    --label-selector 'type=controlplane'
```

### Create the machine configuration files

#### Generating base configurations

Using the IP/DNS name of the load balancer created earlier, generate the base configuration files for the Talos machines by issuing:

```bash theme={null}
talosctl gen config talos-k8s-hcloud-tutorial https://<load balancer IP or DNS>:6443 \
    --with-examples=false --with-docs=false
```

Generating the config without examples and docs is necessary because otherwise you can easily exceed the 32 kb limit on uploadable userdata (see [issue 8805](https://github.com/siderolabs/talos/issues/8805)).

At this point, you can modify the generated configs to your liking.
Optionally, you can specify [machine configuration patches](../../configure-your-talos-cluster/system-configuration/patching#configuration-patching-with-talosctl-cli) which will be applied during the config generation.

#### Validate the configuration files

Validate any edited machine configs with:

```bash theme={null}
talosctl validate --config controlplane.yaml --mode cloud
talosctl validate --config worker.yaml --mode cloud
```

### Create the servers

We can now create our servers.
Note that you can find `IMAGE_ID` in the snapshot section of the console: `https://console.hetzner.cloud/projects/$PROJECT_ID/servers/snapshots`.

#### Create the control plane nodes

Create the control plane nodes with:

```bash theme={null}
export IMAGE_ID=<your-image-id>

hcloud server create --name talos-control-plane-1 \
    --image ${IMAGE_ID} \
    --type cx22 --location hel1 \
    --label 'type=controlplane' \
    --user-data-from-file controlplane.yaml

hcloud server create --name talos-control-plane-2 \
    --image ${IMAGE_ID} \
    --type cx22 --location fsn1 \
    --label 'type=controlplane' \
    --user-data-from-file controlplane.yaml

hcloud server create --name talos-control-plane-3 \
    --image ${IMAGE_ID} \
    --type cx22 --location nbg1 \
    --label 'type=controlplane' \
    --user-data-from-file controlplane.yaml
```

#### Create the worker nodes

Create the worker nodes with the following command, repeating (and incrementing the name counter) as many times as desired.

```bash theme={null}
hcloud server create --name talos-worker-1 \
    --image ${IMAGE_ID} \
    --type cx22 --location hel1 \
    --label 'type=worker' \
    --user-data-from-file worker.yaml
```

### Bootstrap etcd

To configure `talosctl` we will need the first control plane node's IP.
This can be found by issuing:

```bash theme={null}
hcloud server list | grep talos-control-plane
```

Set the `endpoints` and `nodes` for your talosconfig with:

```bash theme={null}
talosctl --talosconfig talosconfig config endpoint <control-plane-1-IP>
talosctl --talosconfig talosconfig config node <control-plane-1-IP>
```

Bootstrap `etcd` on the first control plane node with:

```bash theme={null}
talosctl --talosconfig talosconfig bootstrap
```

After a successful bootstrap, you should see that all the members have joined:

```bash theme={null}
talosctl --talosconfig talosconfig -n <control-plane-1-IP> get members
```

### Retrieve the `kubeconfig`

At this point we can retrieve the admin `kubeconfig` by running:

```bash theme={null}
talosctl --talosconfig talosconfig kubeconfig .
```

### Install Hetzner's Cloud Controller Manager

First of all, we need to patch the Talos machine configuration used by each node:

<CodeBlock filename="patch.yaml">
  {`
    cluster:
    externalCloudProvider:
      enabled: true
    `}
</CodeBlock>

Then run the following command:

```bash theme={null}
talosctl --talosconfig talosconfig patch machineconfig --patch-file patch.yaml --nodes <comma separated list of all your nodes' IP addresses>
```

With that in place, we can now follow the [official instructions](https://github.com/hetznercloud/hcloud-cloud-controller-manager), ignoring the `kubeadm` related steps.
