Network Flow
How traffic flows from the public internet through Bifrost VPS into the Talos cluster — and how LAN clients bypass the VPS entirely.
Overview
Traffic takes one of three paths depending on where the client is and which service they're accessing:
| Path | Client | Route |
|---|---|---|
| Public → Protected | Internet browser | Cloudflare → Bifrost Traefik → Authentik ForwardAuth → WireGuard → Cilium Gateway → Pod |
| Public VPS-native | Internet browser | Cloudflare → Bifrost Traefik → container on VPS (Authentik, NetBird) |
| LAN direct | Home network device | DNS → Cilium L2 LB → Gateway API → Pod |
| VPN remote | NetBird client (laptop/phone) | WireGuard mesh → routing peer → cluster subnet |
DNS Split
The split happens at DNS. Specific hostnames point to Bifrost (Hetzner VPS); everything else resolves to the Cilium L2 LoadBalancer IP on the home network.
| Hostname | DNS A record | Where traffic lands |
|---|---|---|
auth.madhan.app | 178.156.199.250 (Hetzner) | Bifrost — Authentik container |
netbird.madhan.app | 178.156.199.250 (Hetzner) | Bifrost — NetBird dashboard |
proxy.madhan.app | 178.156.199.250 (Hetzner) | Bifrost — NetBird reverse proxy |
grafana.madhan.app | 178.156.199.250 (Hetzner) | Bifrost → WireGuard → Cluster |
harbor.madhan.app | 178.156.199.250 (Hetzner) | Bifrost → WireGuard → Cluster |
*.madhan.app (all others) | 192.168.1.220 (LAN) | Cilium Gateway — LAN only |
Opting in a service to internet exposure: edit the
publicServicesslice incore/cloud/cloudflare.goand runjust core cloudflare up. Traefik routes are auto-generated bygenerateTraefikPublicServices()inhetzner.go.
Full Traffic Diagram
flowchart TB
subgraph INET["Internet"]
BROWSER["Browser / Client"]
CF["Cloudflare DNS<br/>auth · netbird · grafana · harbor"]
end
subgraph BIF["Bifrost VPS · 178.156.199.250"]
TR["Traefik :443<br/>TLS termination<br/>ForwardAuth middleware"]
AUTH["Authentik<br/>SSO · GitHub OAuth<br/>ForwardAuth provider"]
NBS["NetBird Server<br/>management + signal + relay + STUN"]
NBD["NetBird Dashboard"]
end
subgraph WG["WireGuard Mesh"]
NBP["NetBird routing peer<br/>K8s pod · 192.168.1.0/24"]
NBLAP["NetBird client<br/>laptop / phone"]
end
subgraph CLUSTER["Talos Cluster · 192.168.1.0/24"]
GW["Cilium Gateway API<br/>192.168.1.220"]
POD["Service Pods<br/>grafana · harbor · etc."]
end
subgraph LAN["Home LAN"]
LANCLI["LAN browser<br/>DNS → 192.168.1.220"]
end
BROWSER -->|"DNS lookup<br/>grafana.madhan.app"| CF
CF -->|"178.156.199.250"| TR
TR -->|"ForwardAuth check"| AUTH
AUTH -->|"401 → redirect to login<br/>200 → allow request"| TR
TR -->|"authenticated request<br/>via WireGuard"| GW
GW --> POD
BROWSER -->|"auth.madhan.app"| CF
CF -->|direct| AUTH
BROWSER -->|"netbird.madhan.app"| CF
CF -->|direct| NBD
NBS <-->|"WireGuard<br/>tunnel"| NBP
NBS <-->|"WireGuard<br/>tunnel"| NBLAP
NBLAP -->|"192.168.1.0/24<br/>via routing peer"| NBP
NBP --> GW
LANCLI -->|"DNS → 192.168.1.220<br/>no VPS hop"| GW
GW --> POD
Public Request Step-by-Step
When a user opens https://grafana.madhan.app from the internet:
- DNS resolves
grafana.madhan.app→178.156.199.250(Hetzner VPS) - Traefik receives the HTTPS request, terminates TLS (wildcard cert from Cloudflare ACME)
- ForwardAuth middleware sends a sub-request to Authentik:
Is this session authenticated? - Authentik returns:
401— user is redirected tohttps://auth.madhan.appto log in (GitHub OAuth)200— request is forwarded
- Traefik proxies the authenticated request through the WireGuard mesh to the Cilium Gateway (
192.168.1.220) - Cilium Gateway API routes to the Grafana pod via
HTTPRoute
LAN Direct Access
LAN clients resolve *.madhan.app (except the explicitly listed public ones) to 192.168.1.220 — the Cilium L2 LoadBalancer IP. Traffic never leaves the home network.
Bypassing SSO on LAN: For
grafana.madhan.apporharbor.madhan.app, you can override DNS locally to hit the cluster directly and skip the Authentik redirect:# /etc/hosts — bypasses Hetzner + ForwardAuth 192.168.1.220 grafana.madhan.app harbor.madhan.app
VPN Remote Access
From anywhere in the world, a connected NetBird client (laptop or phone) can reach cluster services directly:
- NetBird client connects to the WireGuard mesh via
netbird.madhan.app:443(Traefik → NetBird server) - Routing peer (K8s pod) advertises
192.168.1.0/24into the mesh - Cluster IPs (
192.168.1.220–230) are routable from the client — no browser, no SSO redirect
This is how the devcontainer connects to the cluster remotely.
Traefik Routing in Detail
Traefik on Bifrost uses the file provider only (no Docker provider). Routes are defined in core/cloud/bifrost/traefik/dynamic/:
| File | Contents |
|---|---|
services.yml | Static routes: Authentik, NetBird GRPC, NetBird REST, NetBird dashboard, TCP wildcard for *.proxy.madhan.app |
public-services.yml | Auto-generated by hetzner.go — public homelab routes with ForwardAuth |
public-services.yml is gitignored and regenerated on every just core hetzner up. To expose a new service, add its name to publicServices in core/cloud/cloudflare.go.