From 78c9e7282c8763ad3b99707eb53df375978cea63 Mon Sep 17 00:00:00 2001 From: herbygitea Date: Wed, 6 May 2026 19:00:22 +0000 Subject: [PATCH] Create traefik-deployment.md via n8n --- Tech/Projects/traefik-deployment.md | 188 ++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 Tech/Projects/traefik-deployment.md diff --git a/Tech/Projects/traefik-deployment.md b/Tech/Projects/traefik-deployment.md new file mode 100644 index 0000000..7d23cc8 --- /dev/null +++ b/Tech/Projects/traefik-deployment.md @@ -0,0 +1,188 @@ +--- +project: traefik-deployment +type: project-plan +status: active +path: Tech/Projects +tags: + - homelab + - traefik + - dns + - knot + - tls + - cloudflare + - tailscale + - mcp +created: 2026-05-06 +updated: 2026-05-06 +--- +# Traefik Deployment — Homelab Ingress + +## Goal + +Replace ad-hoc `host:port` access with consistent named endpoints for ~8 +services across three surfaces (public, private, east-west). Build the full +ingress stack — Traefik, Knot DNS, wildcard TLS — and migrate MCP behind it +as the first real service. + +## Locked Architecture + +### Topology + +- **Traefik VM** on PVE (own VM, runs Docker + tailscaled + cloudflared) +- **Knot primary** in LXC on PVE +- **Knot secondary** in Docker on NAS (Ubuntu bare metal) +- **Pi-hole + Unbound** stays as recursive frontend + ad-blocker +- Cross-VLAN firewall locks PVE↔NAS to explicit flows (Knot AXFR, +Traefik→NAS services) + +### DNS + +- Knot authoritative for `herbylab.dev` internal +- Active/passive replication via native AXFR + NOTIFY +- Pi-hole conditional forwards `herbylab.dev` queries to Knot +- Split-horizon: same name resolves to Traefik VM internally, Cloudflare +externally +- Zone file in git, Ansible deploys to primary + +### TLS + +- Wildcard `*.herbylab.dev` via Traefik + Cloudflare DNS-01 +- One cert covers public and private surfaces +- Tailscale certs dropped from the plan + +### Naming + +- Capability-based: `.herbylab.dev` regardless of host +- No `lab.` infix, no `dev.` prefix — domain is by function, not location + +### Routing surfaces + +- **Public:** Cloudflare → cloudflared (on Traefik VM) → Traefik → service +- **Private:** Tailscale → Traefik VM tailnet IP → service (DNS via Pi-hole +→ Knot) +- **East-west:** direct, never through Traefik + +### Visual + + + ┌─────────────────────┐ + │ Cloudflare Edge │ + │ *.herbylab.dev │ + └──────────┬──────────┘ + │ tunnel + tailnet ────────────────────┼──────────────── tailscale0 + │ + ┌──────────▼──────────┐ + │ Traefik VM (PVE) │ + │ - traefik │ + │ - cloudflared │ + │ - tailscaled │ + └──────────┬──────────┘ + │ + ┌────────────────┼────────────────┐ + │ │ │ + ┌───▼──┐ ┌───▼──┐ ┌───▼──┐ + │ MCP │ │whoami│ │ ... │ + └──────┘ └──────┘ └──────┘ + + +## Build Phases + +### Phase 1 — Foundations + +No services routed yet. Each step produces a verifiable artifact. + +- [ ] **1.1 Cloudflare API token** + - Scope: `herbylab.dev` zone, DNS edit permissions only + - Verify with `curl` against CF API + - Store in password manager +- [ ] **1.2 Traefik VM on PVE** + - Fresh Debian or Ubuntu, 1 vCPU, 1GB RAM, on PVE VLAN + - Install Docker, Tailscale; `tailscale up` + - Verify: pingable from laptop over tailnet + - Snapshot +- [ ] **1.3 Knot primary in LXC on PVE** + - Stand up LXC, install Knot + - Load minimal zone file with one test record + - Verify: `dig test.herbylab.dev @` returns expected answer +- [ ] **1.4 Knot secondary in Docker on NAS** + - Configure as AXFR slave + - Open cross-VLAN firewall rule for AXFR + - Verify: `dig test.herbylab.dev @` matches primary +- [ ] **1.5 Pi-hole conditional forwarding** + - Configure Pi-hole: `herbylab.dev` → Knot + - Verify from a normal client: `dig test.herbylab.dev` returns Knot's +answer +- [ ] **1.6 Wildcard cert via Traefik + Cloudflare DNS-01** + - Minimal Traefik config, ACME with DNS-01 challenge + - Request `*.herbylab.dev` + - Verify: cert in `acme.json`, expiry ~90 days out + - No services routed yet + +**Phase 1 checkpoint:** working DNS chain, working wildcard cert, Traefik +VM with no routes. Production unchanged. Safe abort point. + +### Phase 2 — Canary (`whoami`) + +- [ ] **2.1 Deploy `whoami` on Traefik VM** + - Docker compose, labels for both public and private surfaces +- [ ] **2.2 Add DNS records** + - Knot zone: `whoami.herbylab.dev` → Traefik VM tailnet IP + - Cloudflare: `whoami.herbylab.dev` → cloudflared tunnel +- [ ] **2.3 Four-position verification** + - Laptop on tailnet → `https://whoami.herbylab.dev` works, real cert + - Laptop off tailnet (LTE hotspot) → same URL works via cloudflared + - `dig` from inside LAN returns Traefik VM IP + - `dig` from outside returns Cloudflare IP +- [ ] **2.4 Decision: keep `whoami` as permanent diagnostic, or tear down** + +### Phase 3 — MCP Migration + +Parallel-path safety net. Existing `cloudflared → MCP` stays running until +the new path is verified. + +- [ ] **3.1 Add Traefik labels to MCP container** + - Add internal DNS record in Knot + - Existing public path untouched +- [ ] **3.2 Verify private surface first** + - From tailnet: `https://mcp.herbylab.dev` → Traefik → MCP works +- [ ] **3.3 Cut public traffic over** + - Flip Cloudflare DNS / tunnel config to land `mcp.herbylab.dev` on +Traefik + - Verify Claude can still call MCP + - Rollback path: flip DNS back +- [ ] **3.4 Decommission old direct cloudflared route** + - After a few days of stability + +## Open Items (Build-Time Decisions) + +- [ ] Inventory current `*.herbylab.dev` records to migrate from Pi-hole +local DNS into Knot zone file +- [ ] TTL strategy — start at 300 during buildout, raise to 3600 once stable +- [ ] Second Pi-hole instance for redundancy (parallel work, not blocking) +- [ ] Pi-hole sync method — gravity-sync vs Teleporter +- [ ] Ansible role structure for Knot primary deployment + +## Key Principles + +- **Capability-based naming.** Domain reflects function, not host. Service +can move hosts without renaming. +- **Surfaces compose, they don't overlap.** Cloudflare = public auth + ACME +validation. Tailscale = device naming on tailnet. Traefik = service routing +once a request lands. Internal DNS = telling LAN clients where to send the +packet. +- **East-west stays direct.** Service-to-service inside Docker/LAN never +traverses Traefik. +- **Build outside-in for foundations, inside-out for services.** Cert and +DNS first; canary before production. +- **Parallel paths for migration.** Never cut over working infrastructure +without a rollback flip. + +## Reference + +Prior session notes: `Tech/Sessions/homelab-dns-decision.md` (Knot +architecture lock-in, alternatives considered, why split-horizon with +public domain). + + +...sent from Jenny & Travis \ No newline at end of file