Create homelab-dns-decision.md via n8n
This commit is contained in:
parent
9a10c8d1b2
commit
06b836e34a
115
Tech/Sessions/homelab-dns-decision.md
Normal file
115
Tech/Sessions/homelab-dns-decision.md
Normal file
@ -0,0 +1,115 @@
|
||||
---
|
||||
project: homelab-dns-decision
|
||||
type: session-notes
|
||||
status: active
|
||||
path: Tech/Sessions
|
||||
tags:
|
||||
- homelab
|
||||
- dns
|
||||
- knot
|
||||
- pihole
|
||||
- unbound
|
||||
- traefik
|
||||
created: 2026-05-06
|
||||
updated: 2026-05-06
|
||||
---
|
||||
# Homelab DNS — Architecture Decision
|
||||
|
||||
## Outcome
|
||||
|
||||
Locked the internal DNS architecture. **Knot DNS** as a dedicated
|
||||
authoritative server for `herbylab.dev`, running active/passive across
|
||||
Proxmox and the NAS. Pi-hole + Unbound stays as the recursive frontend and
|
||||
ad-blocker.
|
||||
|
||||
## Architecture
|
||||
|
||||
|
||||
Client → Pi-hole (ad-block + cache)
|
||||
├─ herbylab.dev queries → Knot (authoritative)
|
||||
└─ everything else → Unbound → root servers
|
||||
|
||||
|
||||
Split-horizon: `*.herbylab.dev` resolves internally to the Traefik VM IP
|
||||
via Knot. Cloudflare remains public authoritative for the same zone. Same
|
||||
name, different answer depending on who's asking.
|
||||
|
||||
## Decisions
|
||||
|
||||
| Decision | Choice | Rationale |
|
||||
|---|---|---|
|
||||
| Authoritative DNS | Knot DNS | Purpose-built authoritative, native
|
||||
AXFR/NOTIFY, ~40MB RAM, zone files in plain text, `knotc` CLI for runtime
|
||||
changes |
|
||||
| Recursive + ad-block | Pi-hole + Unbound (kept) | Already works, no
|
||||
reason to replace the recursive layer just because zone management is
|
||||
clunky |
|
||||
| Redundancy model | Active/passive, separate hosts | Knot primary on PVE,
|
||||
secondary on NAS — separate failure domains |
|
||||
| Replication | Native AXFR + NOTIFY | Edit primary, secondary auto-syncs.
|
||||
No Ansible run per record change |
|
||||
| Source of truth | Zone file in git | Versioned, debuggable, deployed to
|
||||
primary via Ansible |
|
||||
| Failover at client | OpenWrt DHCP hands out both Pi-hole IPs |
|
||||
Active/passive at the resolver level. Adds the second Pi-hole to the
|
||||
existing pattern |
|
||||
| DHCP | Stays on OpenWrt | Knot doesn't touch DHCP |
|
||||
| Internal certs | Wildcard `*.herbylab.dev` via Traefik + Cloudflare
|
||||
DNS-01 | Real LE cert, no browser warnings, independent of internal DNS
|
||||
layer. Works because Cloudflare owns the public zone — LE never touches the
|
||||
internal IP |
|
||||
| Zone structure | Flat (one zone for all `*.herbylab.dev`) | Single source
|
||||
of truth; revisit if it gets unwieldy |
|
||||
|
||||
## Why Not the Other Contenders
|
||||
|
||||
- **Technitium** — right answer when *replacing* Pi-hole + Unbound
|
||||
entirely. Overkill once Pi-hole stays.
|
||||
- **PowerDNS** — sexy on paper (REST API, web UI option) but adds SQL
|
||||
backend, second daemon for the UI, heavier replication setup. Can write a
|
||||
custom GUI on top of Knot later if needed.
|
||||
- **CoreDNS** — fine, but not really designed for primary/secondary AXFR.
|
||||
Better fit for K8s-style deployments.
|
||||
- **NSD** — close runner-up. Knot edged it on the `knotc` runtime CLI and
|
||||
slightly nicer config syntax.
|
||||
- **BIND9** — overkill, config-heavy. Skipped.
|
||||
|
||||
## Key Learnings
|
||||
|
||||
- **Internal certs are a naming problem, not a DNS problem.** Using `
|
||||
herbylab.dev` for internal names (split-horizon) means a public CA will
|
||||
sign a wildcard cert via DNS-01. A `.lan` or `.internal` zone forces a
|
||||
private CA + root cert distribution — not worth it.
|
||||
- **The job defines the tool.** Original DNS contender list (Technitium,
|
||||
CoreDNS, PowerDNS, BIND9) assumed a full Pi-hole + Unbound replacement.
|
||||
Once Pi-hole stays, the job collapses to "small authoritative server, one
|
||||
zone, easy to replicate" — different list (Knot, NSD, CoreDNS, PowerDNS).
|
||||
- **Cloudflare, Tailscale, Traefik, internal DNS each do one thing.**
|
||||
Cloudflare = public auth + ACME validation. Tailscale MagicDNS = device
|
||||
naming on the tailnet. Traefik = service routing once a request lands.
|
||||
Internal DNS = telling LAN clients where to send the packet. They don't
|
||||
overlap, they compose.
|
||||
|
||||
## Open Items
|
||||
|
||||
- [ ] Stand up second Pi-hole instance for redundancy (parallel to Knot
|
||||
work, not blocking)
|
||||
- [ ] Decide Pi-hole sync method (gravity-sync vs Teleporter)
|
||||
- [ ] Pick deployment unit for Knot on each host (LXC on PVE likely; Docker
|
||||
on NAS)
|
||||
- [ ] Initial zone file content — inventory current `*.herbylab.dev`
|
||||
records to migrate from Pi-hole local DNS
|
||||
- [ ] TTL strategy — start at 300 during buildout, raise to 3600 once stable
|
||||
- [ ] Ansible role for Knot primary deployment
|
||||
- [ ] Confirm Pi-hole conditional forwarding config for `herbylab.dev` →
|
||||
Knot
|
||||
|
||||
## Next Session
|
||||
|
||||
Return to the Traefik chat with this decision locked. DNS slot in the
|
||||
broader stack is now filled.
|
||||
|
||||
|
||||
|
||||
|
||||
...sent from Jenny & Travis
|
||||
Loading…
Reference in New Issue
Block a user