IaC, Kubernetes, Hetzner Cloud, Cloud Native, Terraform, OpenTofu, Kubespray·

Kubernetes on a Hetzner Private Network

Proof of Concept IaC for managing a Kubernetes cluster on Hetzner Cloud

Secure Kubernetes on Hetzner Cloud

Running Kubernetes in the cloud is powerful — but exposing control-plane and worker nodes directly to the internet is risky. This full IaC (Infrastructure as Code) shows a clean, maintainable way to deploy a Kubernetes cluster on Hetzner Cloud where all nodes live exclusively on a private network. sEvery infrastructure element is defined as code using Terraform (OpenTofu) and Kubespray config.

Why This Setup Matters

  • Security first: No public IPs on Kubernetes nodes → reduced attack surface
  • Clean separation: administrator access via dedicated jumphost, outbound internet via gateway, inbound traffic via load balancer
  • Fully automated: Infrastructure as Code + Configuration as Code
  • Clean and Maintainable repository structure
  • Cost-effective: Leverages Hetzner’s affordable cloud resources
  • Repeatable: Clone, re-configure, deploy N+1

Architecture Overview

The setup consists of three layers:

Infrastructure (1-infra)

  • Private Hetzner Cloud network
  • Jumphost (public IP + SSH access)
  • NAT Network gateway (public IP for NAT/outbound traffic)
  • Hetzner Load Balancer (public IP, DNS, TLS termination)

Platform (2-platform)

  • 3-node Kubernetes cluster (all nodes on private IPs only)
  • Deployed automatically with Kubespray
  • Cilium as CNI

Applications (3-apps)

  • HAProxy ingress controller
  • Example deployment (e.g., a simple example.com app) routed through the load balancer

Prerequisites

  • A Hetzner Cloud account and project
  • Terraform / OpenTofu installed
  • Hetzner API token with read & write permissions
  • Basic familiarity with SSH, Kubernetes, ansible, Kubespray

Setup Overview

Clone the repo: https://codeberg.org/tessellative/k8s-hetzner-private-net.git

Follow the setup commands in staging order in the README.md files, starting at the root level.

Setup Order

The repo is nicely split:

  • ./1-infra/terraform/ → Run Terraform to create the network, jumphost, gateway, load balancer and the k8s node VMs.
  • ./2-platform/ → Deploy the 3-node Kubernetes cluster using Kubespray. Access the cluster via sshuttle over the jumphost.
  • ./3-apps/ → Deploy the ingress controller and the sample application
  • Update your /etc/hosts file with the loadbalancer public IP and the DNS: test.example.com
  • Open the http://test.example.com in the browser (This)

Custom DNS with TLS Certificate

This demo can be run behind a proper non-reserved domain with TLS certificates set up! Change and configure the following with your domain

  • ./1-infra/terraform/5-load-balancer.tf
    • Change the LB to serve TLS on port 443 -> resource "hcloud_load_balancer_service" "example-k8s01-lb-service"
    • Configure the http.certificates section -> resource "hcloud_load_balancer_service" "example-k8s01-lb-service"
    • Set the proper DNS name at -> resource "hcloud_managed_certificate" "custom-domain-com"
  • 1-infra/terraform/6-dns-example.com.tf
    • Add the new DNS Zone -> resource "hcloud_zone" "custom-domain-com"
    • Add your DNS record to the zone -> resource "hcloud_zone_rrset" "A-custom-domain-com"

Final Thoughts

This project is a great starting template for anyone who wants to run Kubernetes on Hetzner without exposing nodes publicly. It demonstrates good practices: separation of concerns, private networking and full automation.

Happy deploying! 🚀

contact[at]tessellative.com

Tessellative Solutions • © 2026