Two-layer structure: Sources (raw notes) + Wiki (compile output) Four domains: Dev (40), Venture (3), Homelab (23), Reference (0) Includes CLAUDE.md spec, index pages at all levels, compile log Co-Authored-By: Lovebug <lovebug@herbylab.dev>
189 lines
6.9 KiB
Markdown
189 lines
6.9 KiB
Markdown
---
|
|
created: 2026-05-06
|
|
path: Sources/Homelab
|
|
project: traefik-deployment
|
|
status: active
|
|
tags:
|
|
- homelab
|
|
- traefik
|
|
- dns
|
|
- knot
|
|
- tls
|
|
- cloudflare
|
|
- tailscale
|
|
- mcp
|
|
type: project-plan
|
|
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 |