πŸ“š homelab docs

Homelab Topology & Service Routing

/p/homelab-topology networkdnsreferencearchitecture

Homelab Topology & Service Routing

Three-tier Traefik architecture β€” browser β†’ service request flow, all hosts and networks, all routed services. State as of 2026-07-06 (post-grizzley-replacement migration complete).

1. Physical Hosts

Public Edge

Host IP Role
Internet / Public DNS 47.156.104.83 (apex tophermayor.com) Cloudflare authoritative DNS for *.tophermayor.com
UCG Ultra LAN 192.168.1.1, gateway 192.168.50.1 UniFi OS 5.1.x Β· Network App 10.4.57 Β· 3 internal VLANs (10, 30, 50) Β· LAN DNS resolver + DHCP + DNS Policies

Tier 1 β€” Public Traefik Ingress (LXC)

Host IP Role
CT 102 traefik-lxc 192.168.50.115 (LAN) Β· public IP via UCG Ultra Debian 13 LXC Β· traefik:v3.x systemd service Β· 4 entrypoints: web:80, websecure:443, sshclone:2222, metrics:8080 Β· ~70 HTTP routers + 1 TCP router

Tier 2 β€” Forwarder Layer

Host IP Role
VM 9003 ubuntu 192.168.50.61 Ubuntu 26.04 on PVE Β· own Traefik (public ingress) Β· file-provider forwarders (priority 250) Β· 12 non-migrated containers + postgres-shared (kept for 30-day rollback until 2026-08-06)

Tier 3 β€” New Primary Compute

Host IP Role
Grizzley (NEW) 192.168.50.144 (eth0) Β· 192.168.10.145 (wlan0, VLAN 10) Raspberry Pi 5 Β· Ubuntu 26.04 LTS (Resolute Raccoon, arm64) Β· Docker 29.6.1 + Compose 5.3.0 Β· bare Traefik 3.0.0 (systemd) Β· NOPASSWD sudo for bear

Tier 0 β€” Storage & Other Compute

Host IP Role
TrueNAS 192.168.50.12 NFS server Β· mediadata, backups, traefik-certs
Ice 192.168.50.197 Pi 4 Β· Ubuntu 26.04 Β· Immich + traefik-ice + jump host to PVE
Panda 192.168.30.196 (VLAN 30) Home Assistant OS (smart home control plane)
PVE hypervisor 192.168.50.11 Proxmox VE Β· runs CT 102, CT 104, CT 200, VM 9003 (ubuntu)
CT 104 (this machine) 192.168.50.x Where the docs webapp is hosted Β· LXC container on PVE Β· Hermes default profile
CT 200 media-lxc 192.168.50.200 6c/16GB Β· GPU-passthrough GTX 1080 Β· Jellyfin (29.7x HEVC 1080p transcode)

2. Networks & VLANs

Network Range Notes
Production (VLAN 50) 192.168.50.0/24 gateway 192.168.50.1 Β· 7 clients (truenas, traefik-CT102, termix-CT126, grizzley, hermes-pve-CT104, VectorPro, CT200)
Family / WiFi (VLAN 10) 192.168.10.0/24 grizzley wlan0 .145 lives here
Home Assistant (VLAN 30) 192.168.30.0/24 panda .196
DMZ / Staging (VLAN 40) 192.168.40.0/24
Mgmt / UGC Ultra (VLAN 1) 192.168.1.0/24 UniFi controller at .1
docker bridge (proxy-net) 172.19.0.0/16 grizzley containers & postgres-shared

3. Three-Tier Traefik β€” Static + Dynamic Config Map

Tier 1 β€” CT 102 (public Traefik)

/etc/traefik/traefik.yml (STATIC)
  entryPoints:
    web:80         → redirect→websecure
    websecure:443  β†’ websecure routers
    sshclone:2222  β†’ TCP forwarder β†’ gitea
    metrics:8080
/etc/traefik/dynamic/ (file-provider, hot-reload)
  β”œβ”€β”€ conf.yml                  β€” wildcard cert + dashboards
  β”œβ”€β”€ ubuntu.yml                β€” Host(ubuntu.tophermayor.com) β†’ 192.168.50.61 (legacy)
  β”œβ”€β”€ gitea-ssh-forwarder.yml   β€” TCP :2222 β†’ 192.168.50.144:2222
  β”œβ”€β”€ grizzley-backup-routers.yml
  β”œβ”€β”€ splash-fallbacks.yml      β€” priority-1 fallbacks
  β”œβ”€β”€ media-lxc.yml, pve.yml, truenas.yml, termix.yml, ... β€” 20+ more
  └── 95 DNS policies via UniFi integration v1

Tier 2 β€” ubuntu Traefik (FORWARDER ONLY post-migration)

/etc/traefik/dynamic/
  β”œβ”€β”€ grizzley-auth-forwarder.yml         β€” Host(auth.tophermayor.com) + PathPrefix(/outpost) β†’ grizzley:9000
  β”œβ”€β”€ grizzley-migrated-forwarder.yml     β€” gitea/vaultwarden/navidrome/slskd/homepage β†’ grizzley
  β”œβ”€β”€ upstream-ingress.yml                β€” homepage public routes (repointed to .144)
  └── 12+ other files for ubuntu-local services

Tier 3 β€” grizzley bare Traefik (systemd, NOT docker)

/etc/traefik/traefik.yml (STATIC)
  entryPoints:
    websecure:8443 β†’ app routers
    metrics:8080   (off by default)
/etc/traefik/dynamic/
  β”œβ”€β”€ authentik.yml β€” Host(auth.tophermayor.com) β†’ :9000 + PathPrefix(/outpost) β†’ :9000
  └── (each app's compose has its own service exposure via ports:, NOT via traefik labels)
Auth: CF_DNS_API_TOKEN via /etc/traefik/dns-token.env (chmod 600). Cert: Let's Encrypt DNS-01 via Cloudflare (one cert for auth.tophermayor.com).

4. Request Flow β€” Browser β†’ Service

4a. Generic Public Request (e.g. https://gitea.tophermayor.com)

[Step 1 β€” DNS resolution (client side)]
Browser/curl β†’ Cloudflare DNS β†’ gitea.tophermayor.com β†’ tophermayor.com apex
            β†’ A 47.156.104.83 (public IP)

[Step 2 β€” TCP/TLS handshake + HTTP request (Tier 1)]
Browser β†’ UCG Ultra β†’ CT 102 traefik-lxc :443 (websecure entrypoint)
     β†’ TLS SNI=gitea.tophermayor.com β†’ wildcard cert from acme.json
     β†’ dynamic/ubuntu.yml: Host(gitea.tophermayor.com) β†’ ubuntu-traefik svc (priority 100)

[Step 3 β€” Forwarder (Tier 2)]
ubuntu Traefik :443 β†’ dynamic/grizzley-migrated-forwarder.yml: grizzley-gitea (priority 250)
                β†’ svc gitea-grizzley @ http://192.168.50.144:3000

[Step 4 β€” Backend (Tier 3)]
grizzley .144:3000 β†’ gitea container (port 3000)
Note: Each service may take additional hops. For auth.tophermayor.com, the backend is :9000 (Authentik). For OIDC forwardAuth on OIDC-protected services, the forwardAuth call hops: CT 102 β†’ ubuntu β†’ grizzley :9000 β†’ outpost.goauthentik.io path.

4b. SSH-clone Request (e.g. git clone gitea.tophermayor.com:2222)

client β†’ Cloudflare β†’ 47.156.104.83:2222
      β†’ CT 102 traefik :2222 (sshclone entrypoint, TCP)
      β†’ dynamic/gitea-ssh-forwarder.yml: tcp.routers.gitea-ssh-public
      β†’ svc gitea-ssh @ 192.168.50.144:2222
      β†’ gitea sshd (port 22 in container)

4c. OIDC ForwardAuth on gitea

browser β†’ CT 102 :443
       β†’ router: Host(gitea.tophermayor.com) middlewares=[forwardAuth,local-only]
       β†’ forwardAuth β†’ http://192.168.50.144:9000/outpost.goauthentik.io/auth/traefik
         (hops ubuntu .61 forwarder)
       β†’ Authentik :9000 checks session, returns 302 to /application/o/authorize/
       β†’ If unauthed: 302 β†’ auth.tophermayor.com/application/o/authorize/?...
       β†’ User logs in via Authentik β†’ cookie set β†’ gitea request succeeds

4d. LAN-only request (e.g. https://gitea.local.tophermayor.com)

LAN client β†’ UCG Ultra LAN DNS (192.168.50.1)
          β†’ A 47.156.104.83 (or direct to 192.168.50.115 if UniFi has split-horizon)
          β†’ CT 102 :443
          β†’ router: Host(gitea.local.tophermayor.com) middlewares=[local-only,forwardAuth]
          β†’ Same as 4a from Tier 2 onward

5. Per-Service Routing Table

Hostname Tier 1 (CT 102) Tier 2 (ubuntu forwarder) Tier 3 (grizzley) Backend Auth
gitea.tophermayor.com ubuntu.yml: gitea-public (100) grizzley-migrated-forwarder: grizzley-gitea (250) β€” (direct port) 192.168.50.144:3000 (gitea) forwardAuth β†’ auth.tophermayor.com
gitea.tophermayor.com:2222 TCP: gitea-ssh-public (200) β€” (passthrough) β€” (direct port) 192.168.50.144:2222 (gitea sshd) SSH key (gitea user account)
vaultwarden.tophermayor.com ubuntu.yml: vaultwarden-public (200) grizzley-migrated-forwarder: grizzley-vaultwarden (250) β€” (direct port) 192.168.50.144:80 (vaultwarden) forwardAuth (cookie session)
navidrome.tophermayor.com ubuntu.yml: navidrome-public (200) grizzley-migrated-forwarder: grizzley-navidrome (250) β€” (direct port) 192.168.50.144:4533 (navidrome) navidrome native session
slskd.local.tophermayor.com ubuntu.yml: sldk-local (200) grizzley-migrated-forwarder: grizzley-slskd (250) β€” (direct port) 192.168.50.144:5030 (slskd) slskd native session
homepage.tophermayor.com upstream-ingress.yml: homepage (100) β€” (bypassed, Tier 2β†’Tier 3 direct) β€” (direct port) 192.168.50.144:3003 (homepage) forwardAuth
homepage.local.tophermayor.com upstream-ingress.yml: homepage-local (100) β€” (direct) β€” (direct port) 192.168.50.144:3003 local-only + forwardAuth
homepage-grizzley.local.tophermayor.com upstream-ingress.yml: homepage-grizzley (250) grizzley-migrated-forwarder: grizzley-homepage-grizzley (250) β€” (direct port) 192.168.50.144:3003 forwardAuth
auth.tophermayor.com β€” (CT 102 passes through) grizzley-auth-forwarder: grizzley-authentik (100/2001) β€” (direct port) 192.168.50.144:9000 (authentik) Authentik native login
auth.tophermayor.com/outpost.goauthentik.io/* β€” grizzley-auth-forwarder: outpost router (2001) β€” (direct port) 192.168.50.144:9000 β€” (OIDC forwardAuth endpoint)
immich.tophermayor.com β€” immich route on ubuntu β€” (still on ubuntu) 192.168.50.61:2283 (immich) forwardAuth (immich OIDC)
jellyfin.tophermayor.com ubuntu.yml: jellyfin-public (200) β€” (still on ubuntu) β€” 192.168.50.200:8096 (jellyfin, media-lxc) jellyfin native
traefik-lxc.lan β€” (own host) β€” β€” 192.168.50.115:8080 (metrics) basic-auth
media-lxc.lan media-lxc.yml routes β€” (still on media-lxc, .200) β€” 192.168.50.200 varies

6. DNS Layer Details

Cloudflare (public)

Authoritative for tophermayor.com. 70 records:
  • 3 A-records at public IP 47.156.104.83: opencode, termix, apex tophermayor.com
  • 10 CNAMEs β†’ apex (auth, cloud, gitea, immich, jellyfin, jellyseerr, jfa, minecraft, n8n, vault)
  • ~57 TXT records (_acme-challenge.* for cert validation, DKIM, etc.)
  • navidrome.tophermayor.com added 2026-07-06 (was missing β€” caused the public 502)

UniFi DNS Policies (LAN)

Authoritative for {host}.lan zones. 95 policies total.
Record Old β†’ New Notes
grizzley.lan .84 β†’ .144 Stale fixed 2026-07-06
ct102.lan (missing) β†’ .115 Added 2026-07-06
ubuntu.lan, ice.lan, pve.lan, truenas.lan, media-lxc.lan already correct Verified 2026-07-06
*.local.tophermayor.com (14 service hostnames) .84 β†’ .144 aiomanager, aiometadata, aiostreams, komodo, metrics-grizzley, minecraft*, oc-grizz, reccollection, starfield, status, traefik-grizzley, traefik-wildcard

7. Migration State

Phase A (Authentik) and Phase B (postgres-shared + 5 apps) complete on 2026-07-06. New grizzley (.144) is the new primary. Public Traefik ingress unchanged: CT 102 β†’ ubuntu (file-provider forwarders) β†’ grizzley (:8443 LAN-side). 30-day rollback window: ubuntu containers stopped, data preserved on disk, postgres-shared still running until 2026-08-06.
2 stale records intentionally NOT changed: *.pi.tophermayor.com β†’ .84 (wildcard, separate host) and oc-ice.local.tophermayor.com β†’ .84 (OpenCode on ice, not grizzley).