{"id":13702780,"url":"https://github.com/hcloud-talos/terraform-hcloud-talos","last_synced_at":"2026-05-16T13:01:29.980Z","repository":{"id":227959571,"uuid":"772815777","full_name":"hcloud-talos/terraform-hcloud-talos","owner":"hcloud-talos","description":"This repository contains a Terraform module for creating a Kubernetes cluster with Talos in the Hetzner Cloud.","archived":false,"fork":false,"pushed_at":"2026-05-12T23:01:15.000Z","size":32199,"stargazers_count":334,"open_issues_count":12,"forks_count":65,"subscribers_count":5,"default_branch":"main","last_synced_at":"2026-05-12T23:04:33.350Z","etag":null,"topics":["hcloud","hetzner","hetzner-cloud","kubernetes","talos","talos-linux","talosctl","terraform"],"latest_commit_sha":null,"homepage":"https://registry.terraform.io/modules/hcloud-talos/talos","language":"HCL","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hcloud-talos.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":".github/SUPPORT.md","governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["hcloud-talos"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"lfx_crowdfunding":null,"polar":null,"buy_me_a_coffee":"mrclrchtr","thanks_dev":null,"custom":null}},"created_at":"2024-03-16T00:55:36.000Z","updated_at":"2026-05-12T23:00:15.000Z","dependencies_parsed_at":"2026-05-12T23:01:02.267Z","dependency_job_id":null,"html_url":"https://github.com/hcloud-talos/terraform-hcloud-talos","commit_stats":null,"previous_names":["mrclrchtr/terraform-hcloud-talos","hcloud-talos/terraform-hcloud-talos"],"tags_count":152,"template":false,"template_full_name":null,"purl":"pkg:github/hcloud-talos/terraform-hcloud-talos","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hcloud-talos%2Fterraform-hcloud-talos","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hcloud-talos%2Fterraform-hcloud-talos/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hcloud-talos%2Fterraform-hcloud-talos/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hcloud-talos%2Fterraform-hcloud-talos/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hcloud-talos","download_url":"https://codeload.github.com/hcloud-talos/terraform-hcloud-talos/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hcloud-talos%2Fterraform-hcloud-talos/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33103970,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-16T04:41:52.686Z","status":"ssl_error","status_checked_at":"2026-05-16T04:41:52.009Z","response_time":115,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hcloud","hetzner","hetzner-cloud","kubernetes","talos","talos-linux","talosctl","terraform"],"created_at":"2024-08-02T21:00:42.519Z","updated_at":"2026-05-16T13:01:29.973Z","avatar_url":"https://github.com/hcloud-talos.png","language":"HCL","funding_links":["https://github.com/sponsors/hcloud-talos","https://buymeacoffee.com/mrclrchtr","https://www.buymeacoffee.com/mrclrchtr","https://img.buymeacoffee.com/button-api/?text=Buy%20me%20a%20coffee\u0026emoji=\u0026slug=mrclrchtr\u0026button_colour=FFDD00\u0026font_colour=000000\u0026font_family=Cookie\u0026outline_colour=000000\u0026coffee_colour=ffffff"],"categories":["Tools"],"sub_categories":["Rust"],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003cbr\u003e\n  \u003cimg src=\"https://github.com/hcloud-talos/terraform-hcloud-talos/blob/main/.idea/icon.png?raw=true\" alt=\"Terraform - Hcloud - Talos\" width=\"200\"/\u003e\n  \u003ch1 style=\"margin-top: 0; padding-top: 0;\"\u003eTerraform - Hcloud - Talos\u003c/h1\u003e\n  \u003cimg alt=\"GitHub Release\" src=\"https://img.shields.io/github/v/release/hcloud-talos/terraform-hcloud-talos?logo=github\"\u003e\n  \u003cp\u003e\n    \u003ca href=\"https://hetzner.cloud/?ref=9EF3RYocQW8y\"\u003eNew to Hetzner? Get 20€ credit (and support this project)!\u003c/a\u003e\n  \u003c/p\u003e\n  \u003cp\u003e\n    \u003ca href=\"https://www.buymeacoffee.com/mrclrchtr\"\u003e\u003cimg src=\"https://img.buymeacoffee.com/button-api/?text=Buy%20me%20a%20coffee\u0026emoji=\u0026slug=mrclrchtr\u0026button_colour=FFDD00\u0026font_colour=000000\u0026font_family=Cookie\u0026outline_colour=000000\u0026coffee_colour=ffffff\" alt=\"Buy me a coffee\" /\u003e\u003c/a\u003e\n  \u003c/p\u003e\n  \u003cp\u003eIf this module saved you time or money, consider supporting ongoing maintenance.\u003c/p\u003e\n\u003c/div\u003e\n\n---\n\nThis repository contains a Terraform module for creating a Kubernetes cluster with Talos in the Hetzner Cloud.\n\n- Talos is a modern OS for Kubernetes. It is designed to be secure, immutable, and minimal.\n- Hetzner Cloud is a cloud hosting provider with excellent Terraform support and competitive pricing.\n\n\u003e [!WARNING]\n\u003e This module is under active development. Not all features are compatible with each other yet.\n\u003e Known issues are listed in the [Known Issues](#known-issues) section.\n\u003e If you find a bug or have a feature request, please open an issue.\n\n---\n\n## Goals 🚀\n\n  | Goals                                                              | Status | Description                                                                                                                                                                                                   |\n  |--------------------------------------------------------------------|--------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n  | Production ready                                                   | ✅      | Designed around the recommendations from the [Talos Production Clusters](https://docs.siderolabs.com/talos/latest/getting-started/prodnotes). You still need to handle DNS/LB setup, backups, and operations. |\n  | Use private networks for the internal communication of the cluster | ✅      | Hetzner Cloud Networks are used for internal node-to-node communication.                                                                                                                                      |\n  | Secure API Exposure                                                | ✅      | The Kubernetes and Talos APIs are exposed to the public internet but secured via firewall rules. By default (`firewall_use_current_ip = true`), only traffic from your current IP address is allowed.         |\n  | Possibility to change all CIDRs of the networks                    | ✅      | All network CIDRs (network, node, pod, service) can be customized.                                                                                                                                            |\n  | Configure the Cluster optimally to run in the Hetzner Cloud        | ✅      | This includes manual configuration of the network devices and not via DHCP, provisioning of Floating IPs (VIP), etc.                                                                                          |\n\n## Information about the Module\n\n- A lot of information can be found directly in the descriptions of the variables.\n- You can configure the module to create a cluster with 1, 3 or 5 control planes and n workers or only the control\n  planes.\n- It allows scheduling pods on the control planes if no workers are created.\n- It has [Multihoming](https://docs.siderolabs.com/talos/latest/getting-started/prodnotes/#multihoming) configuration (etcd and kubelet\n  listen on public and private IP).\n- It uses [KubePrism](https://docs.siderolabs.com/talos/latest/kubernetes-guides/advanced-guides/kubeprism)\n  for internal API server access (`127.0.0.1:7445`) from within the cluster nodes.\n- **Public API Endpoint:**\n  - You can define a stable public endpoint for your cluster using the `cluster_api_host` variable (\n    e.g., `kube.mydomain.com`).\n  - If you set `cluster_api_host`, you **must** create a DNS A record for this hostname pointing to the public IP\n     address you want clients to use. This could be:\n     - The Hetzner Floating IP (if `enable_floating_ip = true`).\n     - The IP of an external TCP load balancer you configure separately (pass-through, no TLS termination).\n     - The public IP of a specific control plane node (less recommended for multi-node control planes).\n  - The generated `kubeconfig` will use this hostname if `kubeconfig_endpoint_mode = \"public_endpoint\"`.\n  - The generated `talosconfig` uses direct per-node IPs by default and can optionally use endpoint hostnames via `talosconfig_endpoints_mode`.\n  - **Note:** `cluster_api_host` is the Kubernetes API endpoint (TCP/6443). Talos API access uses TCP/50000 and is\n    configured separately via `talosconfig_endpoints_mode`.\n- **Internal API Endpoint:**\n  - For internal communication _between cluster nodes_, Talos uses an internal API hostname.\n    By default this is `kube.[cluster_domain]` (e.g., `kube.cluster.local`), but you can override it via\n    `cluster_api_host_private`.\n  - If `enable_alias_ip = true` (the default), this module automatically configures `/etc/hosts` entries on each node\n    to resolve the internal API hostname to the _private_ alias IP (`10.0.1.100` by default). This ensures reliable\n    internal communication.\n  - If `enable_alias_ip = false`, you must provide a working private DNS record for `cluster_api_host_private` yourself\n    (or accept the single-node fallback when using a single control plane).\n  - If you access the cluster from a workstation over VPN/private networking, consider creating a private (split-horizon)\n    DNS record for a resolvable name (e.g., `kube.example.com` -\u003e `10.0.1.100`) and set `cluster_api_host_private` to\n    that name. This prevents client-side DNS failures when Talos embeds the internal endpoint into kubeconfig.\n- **Default Behavior (if `cluster_api_host` is not set):**\n  - If you don't set `cluster_api_host`, the generated `kubeconfig` will use an IP address directly as the endpoint\n    (controlled by `kubeconfig_endpoint_mode`, defaulting to the first control plane's public IP or the Floating IP).\n  - `talosconfig` endpoints are configured separately via `talosconfig_endpoints_mode`.\n  - Internal communication will still use the internal API hostname (defaults to `kube.[cluster_domain]`) if `enable_alias_ip = true`.\n- **Private Bootstrap (`bootstrap_endpoint_mode`):**\n  - When running Terraform from a host with VPN/private network access (WireGuard, Tailscale, site-to-site VPN),\n    set `bootstrap_endpoint_mode = \"private_ip\"` so Terraform bootstraps and health-checks the cluster via private IPs\n    instead of public IPs.\n  - Combine with `disable_public_ipv4 = true` to provision nodes without public IPv4 addresses entirely.\n  - See the [WireGuard VPN example](#vpn-only-private-bootstrap-with-wireguard) for a tested setup using Talos's\n    built-in WireGuard.\n\n## Additional installed software in the cluster\n\n### [Cilium](https://cilium.io/)\n\n- Cilium is a modern, efficient, and secure networking and security solution for Kubernetes.\n- [Cilium is used as the CNI](https://docs.siderolabs.com/talos/latest/kubernetes-guides/network/deploying-cilium) instead of the\n  default Flannel.\n- It provides a lot of features like Network Policies, Load Balancing, and more.\n\n\u003e [!IMPORTANT]\n\u003e The Cilium version (`cilium_version`) has to be compatible with the Kubernetes (`kubernetes_version`) version.\n\n\u003e [!TIP]\n\u003e After initial cluster bootstrap, you can set `deploy_cilium = false` (and `deploy_prometheus_operator_crds = false` if you used it) to hand off management to GitOps tools (e.g., Argo CD, Flux).\n\u003e Run `terraform apply` once after toggling: Terraform removes these resources from state without deleting them from the cluster.\n\u003e This works because the module uses `kubectl_manifest` with `apply_only = true`, so Terraform does not delete these manifests on destroy.\n\n### [Hcloud Cloud Controller Manager](https://github.com/hetznercloud/hcloud-cloud-controller-manager)\n\n- Updates the `Node` objects with information about the server from the Cloud, like instance Type, Location, Server ID, IPs.\n- Cleans up stale `Node` objects when the server is deleted in the API.\n- Routes traffic to the pods through Hetzner Cloud Networks. Removes one layer of indirection.\n- Watches Services with `type: LoadBalancer` and creates Hetzner Cloud Load Balancers for them, adds Kubernetes\n  Nodes as targets for the Load Balancer.\n\n\u003e [!TIP]\n\u003e After initial cluster bootstrap, you can set `deploy_hcloud_ccm = false` to hand off management to GitOps tools (e.g., Argo CD, Flux).\n\u003e Run `terraform apply` once after toggling: Terraform removes these resources from state without deleting them from the cluster.\n\u003e This works because the module uses `kubectl_manifest` with `apply_only = true`, so Terraform does not delete these manifests on destroy.\n\n### [Talos Cloud Controller Manager](https://github.com/siderolabs/talos-cloud-controller-manager)\n\n- [Applies labels to the nodes](https://github.com/siderolabs/talos-cloud-controller-manager?tab=readme-ov-file#node-initialize).\n- [Validates and approves node CSRs](https://github.com/siderolabs/talos-cloud-controller-manager?tab=readme-ov-file#node-certificate-approval).\n- In DaemonSet mode: CCM will use hostNetwork and current node to access kubernetes/talos API\n\n### [Tailscale](https://tailscale.com/) (Optional)\n\n- The Talos Image **MUST** be created with the [tailscale extension](https://github.com/siderolabs/extensions/blob/main/network/tailscale/README.md) when `tailscale.enabled` is set to true.\n- Tailscale can be enabled as a system extension on all nodes\n- Provides secure, encrypted networking between your nodes and other devices in your Tailscale network\n- Makes cluster nodes accessible via their Tailscale IPs from anywhere\n- Requires a valid Tailscale auth key to be provided in the configuration\n\n## Prerequisites\n\n### Required Software\n\n- [terraform](https://www.terraform.io/downloads.html)\n- [helm](https://helm.sh/docs/intro/install/)\n\n### Optional Legacy Software\n\n- [packer](https://www.packer.io/downloads) for the deprecated `_packer/` workflow, which is no longer maintained\n\n### Recommended Software\n\n- [hcloud cli](https://github.com/hetznercloud/cli)\n- [talosctl](https://docs.siderolabs.com/talos/latest/introduction/getting-started/#talosctl)\n- [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/)\n\n### Hetzner Cloud\n\n\u003e [!TIP]\n\u003e New to Hetzner Cloud? Use this [referral link](https://hetzner.cloud/?ref=9EF3RYocQW8y) to get **20€ credit** and\n\u003e support this project.\n\n- Create a new project in the Hetzner Cloud Console\n- Create a new API token in the project\n- You can store the token in the environment variable `HCLOUD_TOKEN` or use it in the following commands/terraform\n  files.\n\n## Usage\n\n### 1. Create Talos Images with `terraform-provider-imager` (Recommended)\n\n\u003e [!TIP]\n\u003e You can use official Hetzner Talos ISOs by setting `talos_iso_id_x86` and/or `talos_iso_id_arm` (but these are usually outdated – check the versions!).\n\u003e List Talos ISO IDs: `hcloud iso list`\n\u003e Check the Hetzner changelog for current Talos ISO IDs: https://docs.hetzner.cloud/changelog\n\u003e You can also use custom Talos image by setting `talos_image_id_x86` and/or `talos_image_id_arm`.\n\u003e List Talos image IDs: `hcloud image list`\n\n\u003e [!WARNING]\n\u003e Prefer custom Talos images/snapshots created with `terraform-provider-imager` over the official Hetzner Talos ISOs.\n\u003e Some Hetzner-provided Talos ISOs have booted Talos in `metal` mode instead of `hcloud`, which leads to incorrect\n\u003e node `providerID`s such as `talos://metal/...`. This breaks `hcloud-cloud-controller-manager` route creation and can\n\u003e cause pod-to-pod networking failures across nodes.\n\u003e If you still use `talos_iso_id_x86` or `talos_iso_id_arm`, verify the current ISO version carefully first.\n\u003e See [#417](https://github.com/hcloud-talos/terraform-hcloud-talos/issues/417).\n\nBefore deploying with Terraform, you need Talos OS images (snapshots) available in your Hetzner Cloud project. The maintained workflow is to create those snapshots directly from Terraform with the companion provider [`hcloud-talos/imager`](https://github.com/hcloud-talos/terraform-provider-imager).\n\n```hcl\nterraform {\n  required_providers {\n    imager = {\n      source = \"hcloud-talos/imager\"\n    }\n  }\n}\n\nprovider \"imager\" {\n  token = var.hcloud_token\n}\n\nresource \"imager_image\" \"talos_x86\" {\n  image_url    = \"https://factory.talos.dev/image/\u003cschematic-id\u003e/\u003ctalos-version\u003e/hcloud-amd64.raw.xz\"\n  architecture = \"x86\"\n\n  labels = {\n    version = var.talos_version\n  }\n}\n```\n\nPass the resulting snapshot IDs into this module with `talos_image_id_x86` and `talos_image_id_arm`. A complete runnable example is available in [`examples/extended`](examples/extended).\n\n### 2. Packer Workflow (Deprecated, No Longer Maintained)\n\n\u003e [!WARNING]\n\u003e The `_packer/` workflow is deprecated and no longer maintained. Use [`hcloud-talos/imager`](https://github.com/hcloud-talos/terraform-provider-imager) for new clusters and future updates.\n\n- **Purpose:** Creates ARM and x86 Talos OS snapshots compatible with Hetzner Cloud.\n- **Location:** All Packer-related files are in the `_packer/` directory.\n- **Authentication:** Requires your Hetzner Cloud API token (set the `HCLOUD_TOKEN` environment variable or enter it when prompted by the build script).\n- **Execution:** Run the `create.sh` script from the root of the repository:\n  ```bash\n  ./_packer/create.sh\n  ```\n- **Customization:** You can build standard Talos images or create custom images with additional system extensions using the Talos Image Factory.\n- **Versioning:** Ensure the `talos_version` used during the Packer build matches the `talos_version` variable set in your Terraform configuration to avoid potential incompatibilities.\n\n\u003e **Legacy Instructions:** If you still need the deprecated Packer flow, refer to **[`_packer/README.md`](_packer/README.md)**.\n\n### 3. Deploy the Cluster with Terraform\n\nUse the module as shown in the following working minimal example:\n\n\u003e [!NOTE]\n\u003e Verify version compatibility before deploying:\n\u003e - [Talos Support Matrix](https://docs.siderolabs.com/talos/latest/getting-started/support-matrix)\n\u003e - [Cilium Compatibility](https://docs.cilium.io/en/stable/operations/system_requirements/#kubernetes-versions)\n\n\u003e [!NOTE]\n\u003e Actually, your current IP address has to have access to the nodes during the creation of the cluster.\n\n```hcl\nmodule \"talos\" {\n  source = \"hcloud-talos/talos/hcloud\"\n  # Find the latest version on the Terraform Registry:\n  # https://registry.terraform.io/modules/hcloud-talos/talos/hcloud\n  version = \"\u003clatest-version\u003e\" # Replace with the latest version number\n\n  talos_version = \"v1.12.2\" # The version of talos features to use in generated machine configurations\n\n  # Optional: use official Hetzner Talos ISO IDs (no custom Packer image required)\n  # talos_iso_id_x86 = \"\u003cx86-iso-id\u003e\"\n  # talos_iso_id_arm = \"\u003carm-iso-id\u003e\"\n\n  # Optional: use custom Talos image IDs (snapshots) created by terraform-provider-imager instead\n  # talos_image_id_x86 = \"\u003cx86-image-id\u003e\"\n  # talos_image_id_arm = \"\u003carm-image-id\u003e\"\n\n  hcloud_token            = \"your-hcloud-token\"\n  # If true, the current IP address will be used as the source for the firewall rules.\n  # ATTENTION: to determine the current IP, a request to a public service (https://ipv4.icanhazip.com) is made.\n  # If false, you have to provide your public IP address (as list) in the variable `firewall_kube_api_source` and `firewall_talos_api_source`.\n  firewall_use_current_ip = true\n\n  cluster_name    = \"dummy.com\"\n  location_name   = \"fsn1\"\n\n  control_plane_nodes = [\n    {\n      id   = 1\n      type = \"cax11\"\n    }\n  ]\n}\n```\n\nOr a more advanced example:\n\n```hcl\nmodule \"talos\" {\n  source  = \"hcloud-talos/talos/hcloud\"\n  # Find the latest version on the Terraform Registry:\n  # https://registry.terraform.io/modules/hcloud-talos/talos/hcloud\n  version = \"\u003clatest-version\u003e\" # Replace with the latest version number\n\n  # Use versions compatible with each other and supported by the module/Talos\n  talos_version      = \"v1.12.2\"\n  kubernetes_version = \"1.35.0\"\n  cilium_version     = \"1.16.2\"\n\n  hcloud_token = \"your-hcloud-token\"\n\n  cluster_name     = \"dummy.com\"\n  cluster_domain   = \"cluster.dummy.com.local\"\n  cluster_api_host = \"kube.dummy.com\"\n\n  firewall_use_current_ip = false\n  firewall_kube_api_source = [\"your-ip\"]\n  firewall_talos_api_source = [\"your-ip\"]\n\n  location_name = \"fsn1\"\n\n  control_plane_nodes = [\n    {\n      id   = 1\n      type = \"cax11\"\n    },\n    {\n      id   = 2\n      type = \"cax11\"\n    },\n    {\n      id   = 3\n      type = \"cax11\"\n    }\n  ]\n  control_plane_allow_schedule = true\n\n  worker_nodes = [\n    {\n      id   = 1\n      type = \"cax21\"\n    },\n    {\n      id   = 2\n      type = \"cax21\"\n    },\n    {\n      id   = 3\n      type = \"cax21\"\n    }\n  ]\n\n  network_ipv4_cidr = \"10.0.0.0/16\"\n  node_ipv4_cidr    = \"10.0.1.0/24\"\n  pod_ipv4_cidr     = \"10.0.16.0/20\"\n  service_ipv4_cidr = \"10.0.8.0/21\"\n  \n  # Enable Tailscale integration\n  tailscale = {\n    enabled  = true\n    auth_key = \"tskey-auth-xxxxxxxxxxxx\" # Your Tailscale auth key\n  }\n}\n```\n\n### Endpoint Configuration Examples\n\nThese snippets show only the endpoint- and access-related settings. Combine them with the required module inputs from the examples above.\n\n#### VPN-only (private bootstrap with WireGuard)\n\nUse Talos's built-in [WireGuard](https://www.talos.dev/v1.13/networking/advanced/wireguard/) to bootstrap and manage the cluster over private IPs. No site-to-site VPN VM or external DNS needed.\n\n**On the cluster side** (added as a machine config patch):\n```hcl\ntalos_control_plane_extra_config_patches = [\n  yamlencode({\n    apiVersion = \"v1alpha1\"\n    kind       = \"WireguardConfig\"\n    name       = \"wg0\"\n    privateKey = \"\u003cbase64-node-private-key\u003e\"\n    listenPort = 51820\n    addresses  = [{ address = \"10.200.0.1/24\" }]\n    peers = [{\n      publicKey  = \"\u003cbase64-workstation-public-key\u003e\"\n      allowedIPs = [\"10.200.0.0/24\"]\n    }]\n  })\n]\n\nextra_firewall_rules = [\n  {\n    direction   = \"in\"\n    protocol    = \"udp\"\n    port        = \"51820\"\n    source_ips  = [\"0.0.0.0/0\"]\n    description = \"WireGuard VPN tunnel\"\n  }\n]\n```\n\n**On your workstation** (macOS WireGuard app or `wg-quick`):\n```ini\n[Interface]\nPrivateKey = \u003cbase64-workstation-private-key\u003e\nAddress = 10.200.0.250/24\n\n[Peer]\nPublicKey = \u003cbase64-node-public-key\u003e\nEndpoint = \u003ccontrol-plane-public-ip\u003e:51820\nAllowedIPs = 10.200.0.0/24, 10.0.1.0/24\nPersistentKeepalive = 25\n```\n\nOnce the tunnel is active, switch to private bootstrap:\n```hcl\nbootstrap_endpoint_mode    = \"private_ip\"\nkubeconfig_endpoint_mode   = \"private_ip\"\ntalosconfig_endpoints_mode = \"private_ip\"\n```\n\n\u003e [!NOTE]\n\u003e - The WireGuard patch is applied at node boot time (part of the Talos machine config).\n\u003e - For the initial deployment, bootstrap still uses public IPs (`bootstrap_endpoint_mode = \"public_ip\"`, the default).\n\u003e - After nodes boot with the WireGuard interface active, enable private bootstrap for subsequent operations.\n\n#### VPN-only (site-to-site VPN gateway)\n\nUse this when you already have a VPN gateway VM (`10.0.1.250`) on the same private network, or when you have split-horizon DNS resolving `kube.vpn.example.com` to the private VIP (`10.0.1.100`).\n\n```hcl\nenable_alias_ip            = true # default\ncluster_api_host_private   = \"kube.vpn.example.com\" # -\u003e 10.0.1.100 (private VIP)\n\nbootstrap_endpoint_mode    = \"private_ip\"\nkubeconfig_endpoint_mode   = \"private_ip\"  # uses 10.0.1.100 directly\n# kubeconfig_endpoint_mode = \"private_endpoint\"  # alternative: requires DNS\n\ntalosconfig_endpoints_mode = \"private_ip\"\n\n# Optional: remove public IPv4 entirely\n# disable_public_ipv4      = true\n```\n\n#### Fully Private (no public IPv4)\n\nProvision nodes without public IPv4 addresses for maximum cost optimization and minimum attack surface. Requires the WireGuard tunnel or VPN gateway to be active before applying.\n\n```hcl\nbootstrap_endpoint_mode    = \"private_ip\"\ndisable_public_ipv4        = true\nkubeconfig_endpoint_mode   = \"private_ip\"\ntalosconfig_endpoints_mode = \"private_ip\"\n```\n\n\u003e [!WARNING]\n\u003e When `disable_public_ipv4 = true`, nodes have no public IPs. All Terraform operations (bootstrap, health check, kubeconfig retrieval) use private IPs via the WireGuard tunnel or VPN gateway. Ensure your tunnel is active and reachable before applying.\n\n#### Floating IP (public VIP)\n\nUse this when you want a public, stable Kubernetes API endpoint without running your own load balancer.\n\n```hcl\nfirewall_use_current_ip = true\n\nenable_floating_ip         = true\nkubeconfig_endpoint_mode   = \"public_ip\" # uses the Floating IP for HA control planes\ntalosconfig_endpoints_mode = \"public_ip\"\n```\n\n#### External TCP load balancer + public DNS (recommended for HA)\n\nUse this when you have a dedicated TCP (L4) load balancer pointing to all control planes on port 6443.\n\n```hcl\nfirewall_use_current_ip    = true\n\ncluster_api_host           = \"kube.example.com\" # -\u003e LB IP/DNS\nkubeconfig_endpoint_mode   = \"public_endpoint\"\ntalosconfig_endpoints_mode = \"public_ip\"\n```\n\n#### Split-horizon: public kubeconfig + private node endpoint\n\nUse this when nodes should use a private VIP/hostname, but your kubeconfig should point to a public DNS/LB.\n\n```hcl\nfirewall_use_current_ip    = true\n\nenable_alias_ip            = true # private VIP for nodes\ncluster_api_host_private   = \"kube.internal.example.com\" # -\u003e 10.0.1.100 (private VIP)\n\ncluster_api_host           = \"kube.example.com\" # -\u003e public Floating IP or TCP LB\nkubeconfig_endpoint_mode   = \"public_endpoint\"\ntalosconfig_endpoints_mode = \"public_ip\"\n```\n\n### VPN Site-to-Site (Shared Private Network)\n\nYou can attach a VPN gateway VM (from another Terraform module) to the same private network as the cluster, allowing secure access from remote locations without exposing services to the public internet.\n\n**How it works:**\n- The VPN gateway VM gets a private IP in the cluster's node subnet (e.g., `10.0.1.250`)\n- The gateway does SNAT/masquerading for VPN client traffic, so cluster nodes see traffic as originating from the gateway's private IP\n- No extra routes needed on cluster nodes — return traffic goes directly to the gateway on the same subnet\n\n**Pattern A — Reference the network from another module:**\n\n```hcl\nmodule \"cluster\" {\n  source  = \"hcloud-talos/talos/hcloud\"\n  version = \"\u003clatest-version\u003e\"\n\n  hcloud_token            = \"your-hcloud-token\"\n  firewall_use_current_ip = true\n  cluster_name            = \"vpn-cluster\"\n  location_name           = \"fsn1\"\n\n  control_plane_nodes = [\n    { id = 1, type = \"cax11\" }\n  ]\n}\n\n# In your VPN gateway module, consume the outputs:\nmodule \"vpn_gateway\" {\n  source = \"./vpn-gateway\"\n\n  hcloud_network_id = module.cluster.hetzner_network_id\n  node_subnet_cidr  = module.cluster.node_ipv4_cidr\n  # Give the VPN gateway a private IP in the same subnet, e.g., 10.0.1.250\n}\n```\n\n**Pattern B — Share a network between cluster and VPN modules (BYO network):**\n\n```hcl\n# Create the shared network once\nresource \"hcloud_network\" \"shared\" {\n  name     = \"shared-net\"\n  ip_range = \"10.0.0.0/16\"\n}\n\nresource \"hcloud_network_subnet\" \"shared\" {\n  network_id   = hcloud_network.shared.id\n  type         = \"cloud\"\n  network_zone = \"eu-central\"\n  ip_range     = \"10.0.1.0/24\"\n}\n\n# Pass it to the cluster module\nmodule \"cluster\" {\n  source  = \"hcloud-talos/talos/hcloud\"\n  version = \"\u003clatest-version\u003e\"\n\n  hcloud_token            = \"your-hcloud-token\"\n  firewall_use_current_ip = true\n  cluster_name            = \"vpn-cluster\"\n  location_name           = \"fsn1\"\n\n  # BYO network: module uses this instead of creating a new one\n  network_id       = hcloud_network.shared.id\n  node_ipv4_cidr   = \"10.0.1.0/24\" # must match the existing subnet\n\n  control_plane_nodes = [\n    { id = 1, type = \"cax11\" }\n  ]\n}\n\n# VPN gateway attached to the same network\nmodule \"vpn_gateway\" {\n  source = \"./vpn-gateway\"\n\n  hcloud_network_id = hcloud_network.shared.id\n  node_subnet_cidr  = \"10.0.1.0/24\"\n}\n```\n\n**Adding network routes for VPN client subnets:**\n\nIf you want proper routing (instead of SNAT) between VPN clients and cluster nodes, add static routes to the Hetzner network:\n\n```hcl\nmodule \"cluster\" {\n  # ... other settings ...\n\n  network_routes = [\n    {\n      destination = \"10.8.0.0/24\"   # VPN client subnet\n      gateway     = \"10.0.1.250\"    # VPN gateway private IP\n    }\n  ]\n}\n```\n\n### Mixed Worker Node Types\n\nFor more advanced use cases, you can define different types of worker nodes with individual configurations using the `worker_nodes` variable:\n\n```hcl\nmodule \"talos\" {\n  source  = \"hcloud-talos/talos/hcloud\"\n  version = \"\u003clatest-version\u003e\"\n\n  talos_version      = \"v1.12.2\"\n  kubernetes_version = \"1.35.0\"\n\n  hcloud_token            = \"your-hcloud-token\"\n  firewall_use_current_ip = true\n\n  cluster_name    = \"mixed-cluster\"\n  location_name = \"fsn1\"\n\n  control_plane_nodes = [\n    {\n      id   = 1\n      type = \"cx22\"\n    }\n  ]\n\n  # Define different worker node types\n  worker_nodes = [\n    # Standard x86 workers\n    {\n      id   = 1\n      type = \"cx22\"\n      labels = {\n        \"node.kubernetes.io/instance-type\" = \"cx22\"\n      }\n    },\n    # ARM workers for specific workloads with taints\n    {\n      id    = 2\n      type  = \"cax21\"\n      labels = {\n        \"node.kubernetes.io/arch\"          = \"arm64\"\n        \"affinity.example.com\" = \"example\"\n      }\n      taints = [\n        {\n          key    = \"arm64-only\"\n          value  = \"true\"\n          effect = \"NoSchedule\"\n        }\n      ]\n    }\n  ]\n}\n```\n\n\u003e [!NOTE]\n\u003e The `worker_nodes` variable allows you to:\n\u003e - Mix different server types (x86 and ARM)\n\u003e - Add custom labels to nodes\n\u003e - Apply taints for workload isolation\n\u003e - Control the number of nodes by adding/removing entries\n\u003e - Keep stable node identity by setting `id` (1..N)\n\nYou need to pipe the outputs of the module:\n\n```hcl\noutput \"talosconfig\" {\n  value     = module.talos.talosconfig\n  sensitive = true\n}\n\noutput \"kubeconfig\" {\n  value     = module.talos.kubeconfig\n  sensitive = true\n}\n```\n\nThen you can then run the following commands to export the kubeconfig and talosconfig:\n\n```bash\n# Save the configs to files\nterraform output --raw kubeconfig \u003e ./kubeconfig\nterraform output --raw talosconfig \u003e ./talosconfig\n```\n\nYou can then use `kubectl` and `talosctl` to interact with your cluster.\nRemember to move the generated config files to a persistent location if needed (\ne.g., `~/.kube/config`, `~/.talos/config`).\n\n## Additional Configuration Examples\n\n### Tailscale Integration\n\nThis module supports configuring Tailscale on your cluster nodes, which provides secure networking capabilities:\n\n```hcl\ntailscale = {\n  enabled  = true\n  auth_key = \"tskey-auth-xxxxxxxxxxxx\" # Your Tailscale auth key\n}\n```\n\nWhen Tailscale is enabled:\n- Each node will run Tailscale as a system extension\n- Nodes will automatically connect to your Tailscale network\n- Cilium's loadBalancer acceleration is set to \"best-effort\" mode for compatibility with Tailscale\n- You can access your cluster nodes directly via their Tailscale IPs\n\n\u003e [!NOTE]\n\u003e You must provide a valid Tailscale auth key when enabling this feature. Auth keys can be generated in the Tailscale admin console.\n\u003e For more information, see the [Tailscale documentation on authentication keys](https://tailscale.com/kb/1085/auth-keys/).\n\n### Kubelet Extra Args\n\n```hcl\nkubelet_extra_args = {\n  system-reserved            = \"cpu=100m,memory=250Mi,ephemeral-storage=1Gi\"\n  kube-reserved              = \"cpu=100m,memory=200Mi,ephemeral-storage=1Gi\"\n  eviction-hard              = \"memory.available\u003c100Mi,nodefs.available\u003c10%\"\n  eviction-soft              = \"memory.available\u003c200Mi,nodefs.available\u003c15%\"\n  eviction-soft-grace-period = \"memory.available=2m30s,nodefs.available=4m\"\n}\n```\n\n### Sysctls Extra Args\n\n```hcl\nsysctls_extra_args = {\n  # Fix for https://github.com/cloudflare/cloudflared/issues/1176\n  \"net.core.rmem_default\" = \"26214400\"\n  \"net.core.wmem_default\" = \"26214400\"\n  \"net.core.rmem_max\"     = \"26214400\"\n  \"net.core.wmem_max\"     = \"26214400\"\n}\n```\n\n### Activate Kernel Modules\n\n```hcl\nkernel_modules_to_load = [\n  {\n    name = \"binfmt_misc\" # Required for QEMU\n  }\n]\n```\n\n## Upgrading Kubernetes\n\nThe `kubernetes_version` variable in this Terraform module is used for the _initial deployment_ of your Kubernetes cluster.\nIt does **not** trigger in-place Kubernetes version upgrades on existing nodes.\n\nTo upgrade your Kubernetes cluster, you must use the `talosctl upgrade-k8s` command.\n\n**Important Considerations for `talosctl` commands:**\n\n- **Talos API Endpoints:** `talosctl` talks to the Talos API (TCP/50000). Prefer `talosconfig_endpoints_mode = \"public_ip\"`\n  when running from outside, or `\"private_ip\"` over VPN/private networking. Endpoint hostname modes\n  (`\"public_endpoint\"` / `\"private_endpoint\"`) are also available for explicit gateway/proxy workflows.\n- **Avoid VIP/Load-Balanced Endpoints:** Talos recommends using direct per-node IPs as endpoints in `talosconfig` (not a\n  VIP), because VIP availability depends on etcd health.\n- **Firewall Access:**\n  Ensure your firewall rules (configured via `firewall_use_current_ip` or `firewall_talos_api_source`)\n  allow access to the Talos API port (default 50000) on your control plane nodes from where you are running `talosctl`.\n  Connectivity issues (e.g., `i/o timeout`) can occur if this port is blocked.\n\nRefer to the [official Talos documentation on upgrading Kubernetes](https://docs.siderolabs.com/talos/latest/kubernetes-guides/upgrading-kubernetes) for detailed steps and best practices.\n\n## Known Limitations\n\n- Changes in the `user_data` (e.g. `talos_machine_configuration`) and `image` (e.g. version upgrades with `packer`) will\n  not be applied to existing nodes, because it would force a recreation of the nodes.\n\n## Known Issues\n\n- IPv6 dual stack is not supported by Talos yet. You can activate IPv6 with `enable_ipv6`, but it currently has no\n  effect on the cluster's internal networking configuration provided by this module.\n- Setting `enable_kube_span = true` might prevent the cluster from reaching a ready state in some configurations.\n  Further investigation is needed.\n- `403 Forbidden user` in startup log: This is a known issue related to rate limiting or IP blocking\n  by `registry.k8s.io` affecting some Hetzner IP ranges.\n  See [#46](https://github.com/hcloud-talos/terraform-hcloud-talos/issues/46)\n  and [registry.k8s.io #138](https://github.com/kubernetes/registry.k8s.io/issues/138).\n\n## Support\n\nIf this module saved you time or helped you run Talos on Hetzner more reliably, consider supporting ongoing\nmaintenance:\n\n- [GitHub Sponsors](https://github.com/sponsors/hcloud-talos)\n- [Buy Me a Coffee](https://buymeacoffee.com/mrclrchtr)\n- [Hetzner Cloud referral (20€ credit)](https://hetzner.cloud/?ref=9EF3RYocQW8y)\n\nSponsorship is about sustainability and public appreciation, not a paid support contract or SLA. Sponsors can be\nacknowledged publicly via GitHub Sponsors.\n\n## Credits\n\n- [kube-hetzner](https://github.com/kube-hetzner/terraform-hcloud-kube-hetzner) For the inspiration and the great\n  terraform module. This module is based on many ideas and code snippets from kube-hetzner.\n- [Talos](https://docs.siderolabs.com/talos/latest/overview/what-is-talos) For the incredible OS.\n- [Hetzner Cloud](https://www.hetzner.com/cloud) For the great cloud hosting.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhcloud-talos%2Fterraform-hcloud-talos","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhcloud-talos%2Fterraform-hcloud-talos","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhcloud-talos%2Fterraform-hcloud-talos/lists"}