writing-skills
writing-skills
Use when creating new skills, editing existing skills, or verifying skills work before deployment
hetzner-cloudUse this skill when the user says 'hetzner', 'hetzner cloud', 'hcloud', 'hetzner dedicated', 'hetzner robot', 'hetzner auction', 'hetzner server', 'hetzner kubernetes', 'hcloud terraform', 'hcloud packer', 'hcloud cli', 'CX', 'CCX', 'CAX', 'Hetzner Storage Box', 'Hetzner vSwitch', 'Hetzner Firewall', 'Hetzner Load Balancer', 'Hetzner Floating IP', 'Hetzner …
Independent GitHub snapshot · Automated classification · Original documentation governs
Manage Hetzner Cloud and Dedicated infrastructure: servers, networking, volumes, firewalls, load balancers, Storage Boxes, and Kubernetes clusters. Optimize for cost efficiency while maintaining reliability.
Exact user phrases: "hetzner", "hcloud", "hetzner cloud", "hetzner dedicated", "hetzner robot", "CX", "CCX", "CAX", "hetzner kubernetes", "hcloud terraform", "hcloud packer".
HCL, CLI commands, or configuration files.
Terraform HCL or hcloud CLI commands. No preamble.
400 lines.
Create project → Generate API token → Configure private network → Provision CX52 server with cloud-init → Attach volume → Apply firewall → Set up monitoring. For dedicated: order server via Robot → Configure vSwitch → Install OS via Rescue → Provision.
| Product | Access | Management | Use Case |
|---|---|---|---|
| Cloud (CX/CCX/CAX) | API (hcloud CLI/Terraform) | Full API, self-service | Dynamic workloads, K8s, CI/CD |
| Dedicated (Robot) | Web UI / API (limited) | Manual OS install via Rescue | Stable workloads, large memory, GPU |
| Storage Box | SFTP/WebDAV/CIFS | Manual or scripted | Backups, file shares, Nextcloud |
| Auction | Robot | Same as Dedicated | Deep discounts on older gen HW |
| vSwitch | Robot + Cloud | Layer 2 bridge | Connect Dedicated + Cloud networks |
terraform {
required_providers {
hcloud = {
source = "hetznercloud/hcloud"
version = "~> 1.47"
}
}
}
provider "hcloud" {
token = var.hcloud_token
}
variable "hcloud_token" {
type = string
sensitive = true
}
resource "hcloud_network" "net" {
name = "private-net"
ip_range = "10.0.0.0/16"
}
resource "hcloud_network_subnet" "subnet" {
network_id = hcloud_network.net.id
type = "cloud"
network_zone = "eu-central"
ip_range = "10.0.1.0/24"
}
resource "hcloud_ssh_key" "default" {
name = "default"
public_key = file("~/.ssh/id_ed25519.pub")
}
resource "hcloud_firewall" "web" {
name = "web-firewall"
rule {
direction = "in"
protocol = "tcp"
source_ips = ["0.0.0.0/0", "::/0"]
port = "80"
}
rule {
direction = "in"
protocol = "tcp"
source_ips = ["0.0.0.0/0", "::/0"]
port = "443"
}
rule {
direction = "in"
protocol = "icmp"
source_ips = ["0.0.0.0/0", "::/0"]
}
rule {
direction = "in"
protocol = "tcp"
source_ips = ["10.0.0.0/8"]
port = "22"
}
rule {
direction = "out"
protocol = "tcp"
destination_ips = ["0.0.0.0/0", "::/0"]
port = "443"
}
rule {
direction = "out"
protocol = "udp"
destination_ips = ["0.0.0.0/0", "::/0"]
port = "53"
}
}
resource "hcloud_server" "app" {
name = "app-1"
server_type = "cax21" # ARM, 4 vCPU, 8 GB RAM
image = "ubuntu-24.04"
location = "fsn1" # Falkenstein
ssh_keys = [hcloud_ssh_key.default.id]
firewall_ids = [hcloud_firewall.web.id]
network {
network_id = hcloud_network.net.id
ip = "10.0.1.10"
}
public_net {
ipv4_enabled = true
ipv6_enabled = true
}
# Cloud-init for initial setup
user_data = <<-EOF
#cloud-config
package_upgrade: true
packages:
- docker.io
- docker-compose-v2
- prometheus-node-exporter
runcmd:
- systemctl enable --now docker
- systemctl enable --now prometheus-node-exporter
- ufw allow 9100/tcp
EOF
}
resource "hcloud_volume" "data" {
name = "app-data"
size = 100 # GB
server_id = hcloud_server.app.id
automount = true
format = "xfs"
location = hcloud_server.app.location
delete_protection = true
}
# Format and mount via cloud-init or provisioner:
# mkfs.xfs /dev/disk/by-id/scsi-0HC_Volume_<id>
# mkdir -p /data && mount /dev/disk/by-id/scsi-0HC_Volume_<id> /data
# echo '/dev/disk/by-id/scsi-0HC_Volume_<id> /data xfs defaults,nofail 0 0' >> /etc/fstab
resource "hcloud_load_balancer" "web" {
name = "web-lb"
load_balancer_type = "lb11" # 1.2 TB traffic, 11 connections/sec
location = "fsn1"
network_zone = "eu-central"
}
resource "hcloud_load_balancer_target" "web_app" {
load_balancer_id = hcloud_load_balancer.web.id
type = "server"
server_id = hcloud_server.app.id
use_private_ip = true
}
resource "hcloud_load_balancer_service" "http" {
load_balancer_id = hcloud_load_balancer.web.id
protocol = "http"
listen_port = 80
destination_port = 80
proxyprotocol = false
health_check {
protocol = "http"
port = 80
interval = 15
timeout = 10
retries = 3
http {
path = "/healthz"
}
}
}
resource "hcloud_floating_ip" "vip" {
type = "ipv4"
home_location = "fsn1"
server_id = hcloud_server.app.id
name = "app-vip"
# Failover script handles IP reassignment on server down
}
# Manual failover command:
# hcloud floating-ip assign app-vip app-2
# Automate with keepalived:
# vrrp_instance VIP {
# interface eth0
# virtual_router_id 51
# priority 100
# virtual_ipaddress { <floating_ip_address>/32 }
# }
resource "hcloud_placement_group" "spread" {
name = "spread-group"
type = "spread"
}
resource "hcloud_server" "node" {
count = 3
name = "node-${count.index + 1}"
server_type = "cax21"
image = "ubuntu-24.04"
location = "fsn1"
ssh_keys = [hcloud_ssh_key.default.id]
placement_group_id = hcloud_placement_group.spread.id
}
# Option A: k3s with Hetzner Cloud Controller Manager
# 1. Provision 3+ servers (CX52 or CAX31) with private network
# 2. Install k3s on master
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--disable servicelb --disable traefik" sh -
# 3. Install Hetzner Cloud Controller Manager
kubectl apply -f https://github.com/hetznercloud/hcloud-cloud-controller-manager/releases/latest/download/ccm-networks.yaml
# 4. Install Hetzner CSI Driver for volumes
kubectl apply -f https://raw.githubusercontent.com/hetznercloud/csi-driver/v2.5.0/deploy/kubernetes/hcloud-csi.yml
# 5. Install MetalLB for LoadBalancer IPs (use Floating IP pool)
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.5/config/manifests/metallb-native.yaml
# Option B: Talos Linux on Hetzner (immutable K8s)
# 1. Download talosctl
# 2. Generate config
talosctl gen config k8s-prod https://<control-plane-ip>:6443
# 3. Apply via Talos to each node
# 4. Hetzner Cloud integration via Talos CCM
# Option C: Rancher/RKE2
# 1. Provision servers
# 2. Install RKE2 on control plane nodes
# 3. Join worker nodes
# 4. Deploy Hetzner CCM + CSI
# 1. Order server via Hetzner Robot web UI or auction
# 2. Reboot into Rescue mode
# 3. Install OS with custom config via installimage
installimage -a -c /tmp/install.conf
# install.conf example:
# DRIVE1 /dev/nvme0n1
# DRIVE2 /dev/nvme1n1
# SWRAID 1
# SWRAIDLEVEL 1
# BOOTLOADER grub
# HOSTNAME server1.example.com
# PART /boot ext3 512M
# PART lvm vg0 1G
# LV vg0 swap swap swap 32G
# LV vg0 root / ext4 512G
# IMAGE /root/images/Ubuntu-2404-noble-amd64-base.tar.gz
# 4. Configure vSwitch to bridge with Cloud network
# 5. Install and configure services
# Mount Storage Box via CIFS
sudo mount -t cifs //<username>.your-storagebox.de/backup /mnt/backup \
-o username=<username>,password=<password>,uid=1000,gid=1000,vers=3.0
# Automated backup script (daily cron)
#!/bin/bash
BACKUP_DIR="/mnt/backup/$(date +%Y-%m-%d)"
mkdir -p "$BACKUP_DIR"
tar czf "$BACKUP_DIR/volumes.tgz" /var/lib/docker/volumes
find /mnt/backup -type d -mtime +30 -exec rm -rf {} +
# Borg backup to Storage Box (encrypted, deduplicated)
borg init --encryption=repokey-blake2 ssh://<username>@<username>.your-storagebox.de:23/./backups
borg create --stats --compression lz4 \
ssh://<username>@<username>.your-storagebox.de:23/./backups::$(date +%Y-%m-%d) \
/var/lib/docker/volumes /etc /home
borg prune --keep-daily 7 --keep-weekly 4 --keep-monthly 6 \
ssh://<username>@<username>.your-storagebox.de:23/./backups
# Prometheus + Node Exporter (Hetzer-optimized)
Prometheus config:
- Hetzner servers export metrics via node_exporter
- Hetzner API exporter for server status and costs
- Blackbox_exporter for external endpoint monitoring
Alert rules:
- server_down: up == 0 for 2m
- high_cpu: node_load1 > (cpu_count * 0.8) for 5m
- disk_space: node_filesystem_avail < 10%
- traffic_quota: 80% of monthly included traffic used
- volume_backup_stale: last backup timestamp > 24h
Cost tracking:
- Track bandwidth usage per server (hcloud server describe)
- Budget alerts via script querying hcloud API
- Reserved instances for steady-state workloads
spread to prevent single-rack failure for HA workloads.Server type cost comparison (monthly, fsn1):
CX22 (2 vCPU, 4 GB): ~€4.49
CX32 (4 vCPU, 8 GB): ~€7.49
CX52 (8 vCPU, 16 GB): ~€14.99
CCX13 (2 vCPU, 8 GB, AMD): ~€10.49
CCX23 (4 vCPU, 16 GB, AMD): ~€19.49
CAX11 (2 vCPU, 4 GB, ARM): ~€3.49
CAX21 (4 vCPU, 8 GB, ARM): ~€5.99
CAX31 (8 vCPU, 16 GB, ARM): ~€11.99
Cost-saving strategies:
1. Use CAX (ARM) for most workloads — 30%+ cheaper than equivalent CX
2. Dedicated auction for large-memory needs (512 GB+ RAM)
3. Use included traffic wisely — package vs. individual traffic
4. Single large server vs. multiple small ones — compare total cost
5. Schedule non-production servers to turn off overnight (hcloud server shutdown)
6. Use Storage Boxes for backups vs. dedicated backup servers
7. Volume pricing: 0.06 €/GB/month for block storage
8. Floating IPs: ~€0.50/month each (for HA)
devops-kubernetes for deploying workloads on Hetzner K8s.devops-terraform for Terraform state and module patterns.devops-backup-dr for backup strategies using Storage Boxes.devops-monitoring for Prometheus-based monitoring.devops-hybrid-cloud for connecting Hetzner with other providers.devops-datacenter for physical hardware considerations.| Decision | Dedicated Server (Hetzner) | Cloud Instance (Hetzner Cloud) |
|---|---|---|
| Performance | Full bare-metal (no neighbors) | Virtualized (shared hypervisor) |
| Flexibility | OS from ISO, full control | Pre-installed images, API-driven |
| Provisioning | Hours (manual setup) | Seconds (API, Terraform) |
| Cost | Lower at high utilization | Higher per-hour, pay-as-you-go |
| Autoscaling | Not supported | Supported via API/Terraform |
| Network | Single server, BGP possible | Private network, floating IPs |
| Best for | Workloads needing raw throughput | Variable workloads, ephemeral |
| Aspect | Storage Box (Borg) | Volume Storage (Block) |
|---|---|---|
| Protocol | SFTP, SMB, WebDAV | iSCSI, attached via SCSI |
| Performance | Sequential OK, slow IOPS | Fast IOPS, low latency |
| Capacity | Up to 20 TB | Up to 10 TB per volume |
| Use case | Backups, file shares | Database disks, app data |
| Mountability | Network mount | Direct block device |
| Redundancy | RAID on datacenter side | Replicated across hosts |
resource "hcloud_server" "k3s_control" {
name = "k3s-control-01"
server_type = "cax31"
image = "ubuntu-24.04"
location = "fsn1"
ssh_keys = [hcloud_ssh_key.default.id]
network {
network_id = hcloud_network.main.id
}
user_data = templatefile("${path.module}/cloud-init/control-plane.yaml", {
k3s_token = random_password.k3s_token.result
cluster_cidr = "10.42.0.0/16"
service_cidr = "10.43.0.0/16"
})
}
resource "hcloud_server" "k3s_worker" {
count = 3
name = "k3s-worker-${count.index + 1}"
server_type = "cax21"
image = "ubuntu-24.04"
location = "fsn1"
ssh_keys = [hcloud_ssh_key.default.id]
network {
network_id = hcloud_network.main.id
}
user_data = templatefile("${path.module}/cloud-init/worker.yaml", {
k3s_url = "https://${hcloud_server.k3s_control.ipv4_address}:6443"
k3s_token = random_password.k3s_token.result
})
}
resource "hcloud_network" "main" {
name = "k3s-network"
ip_range = "10.0.0.0/16"
}
resource "hcloud_network_subnet" "main" {
network_id = hcloud_network.main.id
type = "server"
network_zone = "eu-central"
ip_range = "10.0.0.0/16"
}
#!/usr/bin/env bash
HCLOUD_TOKEN="${HCLOUD_TOKEN:?required}"
create_server() {
local name=$1
local type=${2:-cax21}
local location=${3:-fsn1}
curl -sf -X POST "https://api.hetzner.cloud/v1/servers" \
-H "Authorization: Bearer $HCLOUD_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"$name\",
\"server_type\": \"$type\",
\"location\": \"$location\",
\"image\": \"ubuntu-24.04\",
\"automount\": false,
\"networks\": [$(hcloud network list -o noheader -o columns=id | head -1)]
}" | jq '.server'
}
delete_old_snapshots() {
hcloud image list --type snapshot --output json \
| jq -r ".[] | select(.created | fromdate < $(date -d '-30 days' +%s)) | .id" \
| xargs -I {} hcloud image delete {}
}
automount: false and mount volumes directly in fstab with noatimetcp_congestion_control=bbr on all serversspread) for K8s worker anti-affinity across hypervisorsperformance on dedicated servers for latency-sensitive workloadscachefilesd or similar caching layer