Add Hetzner edge node as reverse proxy for custom domains #419

Closed
opened 2026-06-13 07:25:41 +00:00 by ldraney · 0 comments
Owner

Type

Feature

Lineage

Standalone — replaces Cloudflare Tunnel approach from palinks custom-domain spike (ldraney/palinks#15). Uses existing Hetzner account infrastructure.

Repo

ldraney/pal-e-platform

User Story

As a platform operator
I want a Hetzner VPS acting as a reverse proxy with TLS termination
So that custom domains (palinks.app, landscaping-assistant.app, westsidekingsandqueens.com) route to k3s services via Tailscale

Context

The platform currently uses Tailscale Funnel for ingress, which is locked to *.ts.net subdomains. Custom domains need a public-facing edge node with its own IP to terminate TLS (Let's Encrypt via Caddy). The edge node joins the Tailscale network and proxies traffic to k3s services. Lucas already has a Hetzner account (K0624949326) with API access. This is a single VPS serving all custom domains — not one per service.

Architecture:

Browser → custom-domain → Hetzner VPS (public IPv4)
  → Caddy (TLS + Let's Encrypt)
  → Tailscale → k3s cluster → k8s Service

File Targets

Files to create:

  • terraform/modules/hetzner-edge/main.tf — VPS provisioning, SSH key, firewall
  • terraform/modules/hetzner-edge/variables.tf — token, server type, location
  • terraform/modules/hetzner-edge/outputs.tf — public IP, server ID
  • terraform/versions.tf — add hcloud provider
  • terraform/providers.tf — add hcloud provider block
  • terraform/variables.tf — add hetzner_api_token variable
  • terraform/main.tf — wire hetzner-edge module

Files to modify:

  • Makefile — add hetzner_api_token to TF_SECRET_VARS
  • salt/pillar/secrets_registry.sls — add hetzner_api_token metadata
  • salt/pillar/secrets/platform.sls — add hetzner_api_token (GPG-encrypted)

Feature Flag

none

Acceptance Criteria

  • tofu plan shows Hetzner VPS resource to create
  • tofu apply provisions VPS with public IPv4
  • VPS is accessible via SSH
  • Hetzner API token is in Salt GPG pillar, rendered to secrets.auto.tfvars
  • Firewall allows only 80, 443, and SSH

Test Expectations

  • tofu validate passes
  • tofu plan shows expected resources
  • Run command: cd terraform && tofu plan

Constraints

  • Follow existing module pattern (main.tf, variables.tf, outputs.tf, versions.tf)
  • Token flows through Salt GPG → secrets.auto.tfvars pipeline
  • Start with smallest viable VPS (CAX11 ARM — cheapest at ~€3.29/mo)
  • Caddy/SaltStack configuration is a follow-up ticket — this ticket is Terraform provisioning only

Checklist

  • PR opened
  • Tests pass
  • No unrelated changes
  • project-palinks — primary consumer (palinks.app)
  • ldraney/palinks#28 — Configure GoDaddy redirect (superseded by this approach)
  • ldraney/palinks#15 — Custom domain spike (recommended Cloudflare, pivoting to Hetzner)
### Type Feature ### Lineage Standalone — replaces Cloudflare Tunnel approach from palinks custom-domain spike (ldraney/palinks#15). Uses existing Hetzner account infrastructure. ### Repo `ldraney/pal-e-platform` ### User Story As a platform operator I want a Hetzner VPS acting as a reverse proxy with TLS termination So that custom domains (palinks.app, landscaping-assistant.app, westsidekingsandqueens.com) route to k3s services via Tailscale ### Context The platform currently uses Tailscale Funnel for ingress, which is locked to `*.ts.net` subdomains. Custom domains need a public-facing edge node with its own IP to terminate TLS (Let's Encrypt via Caddy). The edge node joins the Tailscale network and proxies traffic to k3s services. Lucas already has a Hetzner account (K0624949326) with API access. This is a single VPS serving all custom domains — not one per service. Architecture: ``` Browser → custom-domain → Hetzner VPS (public IPv4) → Caddy (TLS + Let's Encrypt) → Tailscale → k3s cluster → k8s Service ``` ### File Targets Files to create: - `terraform/modules/hetzner-edge/main.tf` — VPS provisioning, SSH key, firewall - `terraform/modules/hetzner-edge/variables.tf` — token, server type, location - `terraform/modules/hetzner-edge/outputs.tf` — public IP, server ID - `terraform/versions.tf` — add hcloud provider - `terraform/providers.tf` — add hcloud provider block - `terraform/variables.tf` — add hetzner_api_token variable - `terraform/main.tf` — wire hetzner-edge module Files to modify: - `Makefile` — add hetzner_api_token to TF_SECRET_VARS - `salt/pillar/secrets_registry.sls` — add hetzner_api_token metadata - `salt/pillar/secrets/platform.sls` — add hetzner_api_token (GPG-encrypted) ### Feature Flag none ### Acceptance Criteria - [ ] `tofu plan` shows Hetzner VPS resource to create - [ ] `tofu apply` provisions VPS with public IPv4 - [ ] VPS is accessible via SSH - [ ] Hetzner API token is in Salt GPG pillar, rendered to secrets.auto.tfvars - [ ] Firewall allows only 80, 443, and SSH ### Test Expectations - [ ] `tofu validate` passes - [ ] `tofu plan` shows expected resources - Run command: `cd terraform && tofu plan` ### Constraints - Follow existing module pattern (main.tf, variables.tf, outputs.tf, versions.tf) - Token flows through Salt GPG → secrets.auto.tfvars pipeline - Start with smallest viable VPS (CAX11 ARM — cheapest at ~€3.29/mo) - Caddy/SaltStack configuration is a follow-up ticket — this ticket is Terraform provisioning only ### Checklist - [ ] PR opened - [ ] Tests pass - [ ] No unrelated changes ### Related - `project-palinks` — primary consumer (palinks.app) - `ldraney/palinks#28` — Configure GoDaddy redirect (superseded by this approach) - `ldraney/palinks#15` — Custom domain spike (recommended Cloudflare, pivoting to Hetzner)
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ldraney/pal-e-platform#419
No description provided.