Exposure
Every service is LAN-only by default: the Cloudflare wildcard *.madhan.app points at the private Gateway IP. A service becomes public only when it is listed in publicServices in cloudflare.go.
How it fits
Section titled “How it fits”flowchart LR
subgraph DNS[Cloudflare, DNS-only]
W["*.madhan.app → 192.168.1.220"]
P["public names → 178.156.199.250"]
end
W --> G[Cilium Gateway]
P --> T[Traefik on Bifrost] -->|ForwardAuth unless SkipAuth| N[NetBird tunnel] --> G
All records are DNS-only (not proxied). A specific record beats the wildcard, so deleting a service’s record is all it takes to make it private again: its name falls back to 192.168.1.220, which the internet cannot reach.
What is reachable where
Section titled “What is reachable where”| Name or address | LAN | VPN (NetBird) | Public |
|---|---|---|---|
auth, netbird, uptime, proxy, *.proxy | Yes | Yes | Yes, served by Bifrost itself |
grafana | Yes | Yes | Yes, Grafana’s own Authentik login (SkipAuth: true) |
argocd, hubble (Pulumi HTTPRoutes) | Yes | Yes | No |
longhorn, openbao, harbor, n8n, headlamp, falco, vmselect, alertmanager, victorialogs, kubeflow, ollama, comfyui | Yes | Yes | No |
192.168.1.225:11434 (Ollama LoadBalancer) | Yes | Yes | No |
192.168.1.230 (CS2 LoadBalancer) | Yes | Yes | No |
Hostnames are <name>.madhan.app. VPN reach comes from the 192.168.1.0/24 route that k8s-routing-peer advertises to the All group; the public wildcard record makes the same names resolve off-LAN. ComfyUI ships scaled to zero.
Make a service public
Section titled “Make a service public”-
Add it to
publicServicesincloudflare.go. SetSkipAuth: trueonly if the app does its own Authentik login.core/cloud/cloudflare.go var publicServices = []PublicService{{Name: "grafana", SkipAuth: true},{Name: "headlamp"},} -
Run
just core hetzner up. It regeneratestraefik/dynamic/public-services.yml(gitignored) and Traefik hot-reloads the router. -
Run
just core cloudflare upto create the A record pointing at178.156.199.250. -
Check:
dig +short headlamp.madhan.appreturns178.156.199.250, and an unauthenticatedcurl -I https://headlamp.madhan.appredirects toauth.madhan.app.
To make it private again, remove the entry and run the same two commands. Never edit public-services.yml or services.yml for this; the first is regenerated on every run.
Temporary sharing
Section titled “Temporary sharing”netbird expose <port> from any mesh peer publishes <name>.proxy.madhan.app through Traefik TCP passthrough and netbird-proxy, without DNS changes and without Authentik. See NetBird.
Troubleshooting
Section titled “Troubleshooting”A service is still public after removing it
Section titled “A service is still public after removing it”The A record still exists: run just core cloudflare up. Resolvers may cache the old answer until its TTL expires.
Public name resolves but returns 504
Section titled “Public name resolves but returns 504”Traefik has the route but cannot reach the Gateway. See Bifrost troubleshooting.