Architecture¶
How the homelab is wired — host, topology, routing, and data flows. For domain context,
conventions, and gotchas see CONTEXT.md (the two are complementary and both kept current);
for the full per-stack inventory see the Stacks table in CONTEXT.md.
Single host. Everything runs on av as Docker Compose stacks (plus vLLM as user
systemd units). The earlier Pi (Zigbee/Thread/voice/etc.) has been fully migrated off and
retired — the Pi was rebuilt into an unrelated retro console (docs/retro-box/).
Host: av (ascertain-verse)¶
Framework Desktop · AMD Strix Halo "AI Max+ 395" · 128 GB unified RAM (~96 GB usable as VRAM) ·
CachyOS (Arch) · LUKS root. Admin user skill-lava (docker group; sudo requires a password —
automation never uses it). A guest-account Sunshine/Moonlight gaming setup shares the box and
is off-limits (docs/gaming/): don't touch display/session/udev config or ports 47984–48010 / 1716.
Layers¶
Internet / tailnet
│
AdGuard DNS ─────────┤ *.fmm.house → av LAN IP (local fallback)
(LAN :53) │
▼
Traefik :80/:443 ── *.fmm.house wildcard TLS (LE DNS-01 / porkbun)
│ owns the `proxy` bridge net (172.30.0.0/24)
│
┌──────┴───────────────── two routing paths ─────────────────┐
│ container labels (most services) │ file provider (a few) │
│ traefik.* on the proxy network │ dynamic/*.yaml → │
│ │ 192.168.86.49:<2001-2005>│
▼ ▼
apps: ha · homarr · forgejo · arcane · immich · matrix · ocis · peertube · frigate ·
audiobookshelf · calibre-web-automated · beszel · uptime-kuma · postmoogle ·
music · trmnl · …
│
▼
data: bind-mounted config (committed) · runtime in named volumes / gitignored data/
- Edge: AdGuard (LAN DNS + ad-block) → Traefik (reverse proxy + TLS). Traefik reaches
host-networked services (HA, matter-server) over the
proxybridge. - Routing: most services are reached only via Traefik container labels and publish no
host port. A few are routed by file-provider entries (
stacks/traefik/dynamic/*.yaml) pointing at a published host port in 2001–2100. Protocol ports (53/80/443/1883/…) are pinned. Full map:docs/host-port-map.md. - Data: one compose file per stack (
stacks/<name>/compose.yml, explicitname:); bind- mounted config is committed, runtime data lives in volumes or gitignoreddata/.
Home Assistant + radios¶
HA runs host-networked on av. Zigbee via Zigbee2MQTT (ZBT-2 coordinator; migrated
from the Pi's ZHA without re-pairing — docs/runbooks/zigbee-migration.md). Mosquitto is
the MQTT bus (LAN only). Matter server runs on av; a Thread border router (otbr) is
defined in stacks/ha but not yet set up (its SkyConnect radio isn't connected) —
docs/runbooks/thread-multiprotocol.md.
Local-AI pipeline¶
vLLM serves the local LLM (Ornith) as user systemd units (not compose):
:8001 model → :8002 think-proxy → :8010 tool-router. Two consumers: the HA voice
pipeline (STT faster-whisper / TTS piper) and the pi coding agent (code.fmm.house).
Full detail — model, proxies, MCP tool-shaping — in CONTEXT.md.
Observability¶
up.fmm.house (Uptime Kuma, availability) · bz.fmm.house (Beszel hub + local agent, metrics
& btop-style container view) · Diun (image-update notifier → Matrix + MQTT→HA phone push) ·
Docker HEALTHCHECKs on ~every container. Backups are not set up yet (deferred to a
separate off-box target). See docs/runbooks/monitoring.md.
Network model (fmm.house)¶
- DNS — one public answer, reachable two ways (split-horizon retired 2026-07-16; full spec
docs/runbooks/dns.md): the porkbun zone is authoritative for everyone, publishing per nameA *.fmm.house/apex →192.168.86.49(LAN IPv4) and twoAAAAs —fd00:6176::49(av's LAN IPv6 ULA) andfd7a:…:4001:3507(av's native Tailscale IPv6). The two LAN addresses are reached on-LAN directly or off-LAN via an approved Tailscale subnet route (192.168.86.49/32+fd00:6176::49/128);fd7ais the fallback for peers those routes don't reach — route-less peers and node shares (subnet routes don't cross a share). Every service is thus reachable on the LAN or the tailnet and nothing is internet-routable (all private; no port-forward; the wildcard hides the service list). Traefik does all routing + the apex→www redirect. AdGuard's rewrite (A .49+AAAA fd00:6176) is now just a local fallback for the WAN-down case, not load-bearing. A publicfmm.house MX 10 mail.fmm.houseroutes remote-tailnet mail to Postmoogle (:25); external senders resolve to the private addresses and fail (no public inbound mail). Note: AdGuard's UI rewrites are A/AAAA/CNAME-only and shadow other record types, so internal mail uses the apex A as an implicit MX rather than an internal MX record (docs/runbooks/email-to-matrix.md, ADR-0006). - TLS: one
*.fmm.houseLet's Encrypt wildcard, DNS-01 via the porkbun API (stacks/traefik/.env), auto-renewed by Traefik. - Tailscale: tailnet
anaconda-perch.ts.net; av =100.98.53.7. - ufw: LAN-scoped for 80/443/53/8123,
tailscale0for 80/443, and a blanket allow from the proxy bridge172.30.0.0/24(covers new Traefik-fronted services). Added manually with sudo.
Known cruft (cleanup at leisure, needs sudo)¶
av:~/ai/— root-owned empty mount-skeleton dirs from a deleted compose project (containers adopted intostacks/ai).~/homelab/comfy-env(~18 GB) +~/homelab/.venv— legacy ComfyUI python envs, gitignored.- The pre-repo compose draft is preserved at
docs/attic/ai-compose-draft.yml.