Einmalig / pro Projekt / pro Server
| Was | wie oft |
|---|---|
Hetzner-Konto, 2FA, Zahlungsart, hcloud CLI + Kontext, SSH-Key auf dem Laptop, die Skripte klonen | einmal |
| Projekt anlegen, API-Token, SSH-Key bei Hetzner hinterlegen, Firewall(s) anlegen | pro Projekt (prod / staging / Wegwerf getrennt) |
cloud-init schreiben, cloud.ps1/snapshot.ps1 konfigurieren, Server erstellen, DNS (Domain Name System) setzen | pro Server / Dienst |
Diese Anleitung deckt Zeile 1 und 2 ab und zeigt das Muster für Zeile 3. Die konkrete cloud-init-Zeile und den Stack liefert die jeweilige Dienst-Anleitung.
1 Konto, 2FA, Projekt
- Konto auf console.hetzner.com,
Zahlungsart hinterlegen (sonst schlägt
server createmit „not verified“ fehl). - 2FA anschalten: – das Hetzner-Konto steuert echtes Geld.
- Projekt anlegen (oben links). Ein Projekt ist ein abgeschotteter
Ressourcen-Topf mit eigenem Token. Empfehlung: eigenes Projekt
heimlaborfür Wegwerf-Server, getrennt von allem Dauerhaften.
Hetzner hat keine Kostenobergrenze. Der wichtigste Schutz ist der Kill-Switch aus Server-Snapshot & Restore – und die Grundausstattung-Checkliste.
2 API-Token
Das Passwort, mit dem hcloud Server erstellt und löscht. Pro
Projekt einer.
- Im Projekt: → Berechtigung Read & Write.
- Sofort kopieren – wird nur einmal gezeigt. In den Passwortmanager, nicht ins Repo.
- Verloren? Neuen erzeugen, alten löschen.
3 hcloud CLI & Kontext
winget install HetznerCloud.CLI
# Terminal neu oeffnen, damit hcloud im PATH ist
hcloud context create heimlabor # fragt nach dem Token aus Schritt 2
hcloud context list # der aktive hat einen *
OK wenn: hcloud server list eine (leere) Tabelle zeigt statt
einer Fehlermeldung. Mehr Projekte → hcloud context create <name> und
hcloud context use <name>.
4 Werkzeuge je nach Ziel
| Du willst… | zusätzlich |
|---|---|
| Container (Podman/Docker) fernsteuern | winget install RedHat.Podman oder Docker Desktop |
| Kubernetes (k3s) | winget install Kubernetes.kubectl Helm.Helm, optional Derailed.k9s |
| OpenShift/OKD (auch OKD Single-Node) | oc von mirror.openshift.com, für OKD Single-Node zusätzlich wsl --install |
| Nur Web-Dienste (WordPress, Nextcloud, Mail …) | nichts extra – alles läuft per SSH (Secure Shell) auf dem Server |
| Dateien synchron halten | winget install cwRsync (sonst tar+ssh, unter Windows 10 dabei) |
ssh.exe, scp.exe, tar.exe sind unter Windows 10/11
eingebaut – nichts zu installieren.
5 SSH-Key hinterlegen
Der private Teil bleibt auf dem Laptop, der öffentliche wird von Hetzner in
den root-Account jedes neuen Servers gelegt.
# nur falls noch keiner existiert:
ssh-keygen -t ed25519 -f $env:USERPROFILE\.ssh\id_ed25519
# bei Hetzner hinterlegen (Name "laptop" -- taucht in jedem "server create" wieder auf)
hcloud ssh-key create --name laptop --public-key-from-file $env:USERPROFILE\.ssh\id_ed25519.pub
Der SSH-Key ist projektgebunden – in jedem neuen Projekt einmal
hcloud ssh-key create mit demselben .pub.
6 Firewall-Prinzip
Eine Hetzner Cloud Firewall filtert schon im Netz, vor dem Server. Regel: nur die nötigen Ports, nur von deiner IP (Internet Protocol).
Wer darf womit reden – und warum die Firewall vor dem Server sitzt und nicht darauf.
Zeichnung aus Diagramme: Hetzner & CRC (02).
$ip = (Invoke-RestMethod 'https://ipv4.icanhazip.com').Trim()
hcloud firewall create --name podman-host-fw
hcloud firewall add-rule podman-host-fw --direction in --protocol tcp --port 22 --source-ips "$ip/32"
# je nach Dienst zusaetzlich: 6443 (kube-API), 80/443 (Web) -- steht in der Dienst-Anleitung
cloud.ps1 / snapshot.ps1 setzen bei jedem up die
SSH-Regel auf deine aktuelle IP – kein Nachjustieren, wenn die Heim-IP
wechselt. Noch besser: Tailscale
rein und Port 22 ganz zu.
7 cloud-init: Grundmuster
cloud-init.yaml läuft einmalig beim ersten Boot.
Jede Dienst-Anleitung ergänzt hier ihre eine Zeile (k3s-Install, Podman, Docker …) –
der Rahmen ist immer gleich.
#cloud-config
package_update: true
package_upgrade: true
packages: [ ca-certificates, curl, fail2ban, unattended-upgrades ]
runcmd:
# --- HIER die Zeile aus der jeweiligen Dienst-Anleitung ---
# Container: - curl -fsSL https://get.docker.com | sh
# k3s: - curl -sfL https://get.k3s.io | sh -s - server --write-kubeconfig-mode 644 --tls-san "$(curl -s http://169.254.169.254/hetzner/v1/metadata/public-ipv4)"
- printf '%s\n' '{ "log-driver":"json-file","log-opts":{"max-size":"10m","max-file":"3"} }' > /etc/docker/daemon.json
- systemctl restart docker 2>/dev/null || true
write_files:
- path: /etc/apt/apt.conf.d/20auto-upgrades
content: |
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
Wichtig: cloud-init läuft nur beim
allerersten up (aus dem Basis-Image). Danach bootet der Server aus dem
Snapshot und alles ist schon eingerichtet – siehe
Server-Snapshot & Restore.
8 Die Skripte konfigurieren
Repo klonen (Link in der Übersicht),
dann oben in der passenden .ps1 die Konfiguration anpassen.
| Skript | wofür | wichtigste Felder |
|---|---|---|
cloud.ps1 | ein Server, einfacher An/Aus | $Server, $Type (CPX32), $SshKey, $Firewall, optional $DnsZone/$DnsNames |
snapshot.ps1 | mehrere Server / Cluster, feste IP optional | je Server ein @{ name; type; firewall; ddnsZone; ddnsNames; preDown; postUp } |
dienste-stack.ps1 | Compose-Stacks auf dem konsolidierten Server | $Server (SSH-Alias), $StacksDir, $UpOrder |
Ohne feste IP: $DnsZone/ddnsZone setzen, dann ruft das Skript
beim up cf-dns.ps1 auf und zieht den A-Record auf die neue IP nach.
Token einmal in $env:CF_API_TOKEN.
9 Erster Server
.\scripts\cloud.ps1 up # erstellt aus Basis-Image + cloud-init, setzt Firewall + Verbindung
.\scripts\cloud.ps1 status # laeuft er? seit wann? ~Kosten
.\scripts\cloud.ps1 ssh # SSH drauf (Host-Key wird vorher bereinigt)
Der erste SSH-Kontakt bestätigt automatisch den neuen Host-Key. Bei
„resource_unavailable“ hat Hetzner keine Kapazität für den Typ –
.\scripts\cloud.ps1 up -Type cpx42 (eine Nummer größer), einen anderen
Standort (-Location hel1) oder – falls gerade frei – -Type cx33 /
cax21 probieren.
Server steht. Jetzt zurück in die jeweilige Anleitung – ab dem Punkt „Container starten“ / „kubeconfig holen“ / „compose-Stack“.
10 Danach: was noch
Drei Wege hinein – und welcher noch funktioniert, wenn die anderen versagen.
Zeichnung aus Diagramme: Hetzner & CRC (08).
| Thema | Anleitung |
|---|---|
| Server abbauen, Snapshot, löschen, Kill-Switch, Kosten stoppen | Server-Snapshot & Restore |
| Kein offener Port mehr – SSH/kubectl/DB übers Mesh | Tailscale |
| Mehrere Dienste hinter einem Reverse Proxy | Mehrere Dienste auf einem Server |
| Verschlüsselte Backups mit getestetem Restore | Backup & Restore |
| Erfahren, wenn etwas kaputt ist | Monitoring & Alerting |
| 2FA, Recovery, Impressum, Log-Limits … die Dinge, die man vergisst | Grundausstattung |
| Wo im Webpanel was liegt | Hetzner Cloud Console |
+ Ohne Skripte (reine hcloud-Befehle)
$ip = (Invoke-RestMethod 'https://ipv4.icanhazip.com').Trim()
hcloud firewall replace-rules podman-host-fw --rules-file rules.json # [{"direction":"in","protocol":"tcp","port":"22","source_ips":["'+$ip+'/32"]}]
hcloud server create --name podman-host --type cpx32 --image ubuntu-24.04 `
--ssh-key laptop --firewall podman-host-fw --user-data-from-file cloud-init.yaml
hcloud server ip podman-host
# ... arbeiten ...
hcloud server create-image --type snapshot --description podman-host podman-host
hcloud server delete podman-host
Die Firewall-Regeldatei ohne BOM
schreiben ([IO.File]::WriteAllText, nicht Set-Content -Encoding utf8) –
sonst scheitert der JSON-Parser von hcloud.
+ Mehrere Server & Projekte
- Getrennte Projekte für prod / staging / Wegwerf – eigene Tokens,
eigene Rechnung, kein versehentliches Löschen über Grenzen. Der Self-Destruct-Timer
(
cloud-init.selfdestruct.yaml) nur in einem reinen Wegwerf-Projekt. - Ein Server für viele Dienste statt vieler kleiner – Mehrere Dienste auf einem Server.
- Cluster (mehrere Nodes):
snapshot.ps1mit mehreren$SERVERS-Einträgen, feste Primary-IPs optional. - Namenskonvention:
<zweck>-host(podman-host,k3s-host), Firewall<zweck>-fw– die Skripte leiten das ab.