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
:443and 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.devrecord points app hostnames at the edge vps; cert-manager uses dns-01 withCF_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.