architecture

components

  • edge vps: generic debian host with a public ipv4 address, tailscale, and haproxy.
  • lab node: debian 13 machine running k3s single-server mode and tailscale.
  • network: haproxy binds vps public ipv4 :443 and forwards raw tcp over tailscale to the lab node.
  • ingress: caddy runs inside k3s and terminates tls using cert-manager-issued secrets.
  • dns: cloudflare hosts public dns. a wildcard *.k3x.dev record points app hostnames at the edge vps; cert-manager uses dns-01 with CF_DNS_API_TOKEN.
  • gitops: flux applies kustomize trees from this repo.
  • secrets: sops + age encrypted secrets committed to git.

traffic flow

public path

client https://app.k3x.dev
  -> cloudflare wildcard dns returns edge vps public ipv4
  -> haproxy accepts tcp :443 on vps
  -> haproxy forwards tcp to <lab-node-tailscale-ip>:443
  -> caddy hostNetwork pod receives tls on lab node :443
  -> caddy proxies to service

local / tailscale split-horizon path

local or tailscale client https://app.k3x.dev
  -> local wildcard override returns lab node tailscale ip
  -> caddy hostNetwork pod receives tls on lab node :443
  -> caddy proxies to service

haproxy does not inspect tls, terminate tls, or need certificates. local users bypass haproxy but still use the same hostname, cert, and caddy config.

caddy over traefik

k3s ships traefik by default, but this repo disables it and runs caddy via plain manifests. caddy is preferred here because the desired shape is simple tls termination and reverse proxying, and caddy config stays easier to read than ingress-controller-specific crd sprawl.