115 lines
4.2 KiB
Markdown
115 lines
4.2 KiB
Markdown
---
|
|
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 |