Skip to content

Deprecated — Migrated to NET-0001 on 2026-05-02 per ADR-0047. This source file is retained as a reference; the canonical content is in NET-0001.

ADR-0041 — DNS Architecture: Internal Resolution Strategy

| Status | Accepted | | Date | 2026-04-27 | | Author | Ben Peries | | Phases | 2, 3 | | ADO WI | WI-364 | | Supersedes | None | | Related | ADR-0004 (OPNsense KVM), WI-248 (Technitium migration), WI-345 (CoreDNS bridge), WI-305 (Tailscale DNS on Docker hosts) | | Epic | E1 — Platform Infrastructure & Ops (WI-257) |

Context

Discovery scope

This ADR documents the DNS architecture discovered on 2026-04-27 during infrastructure discovery. No service changes were made during this review. The ADR records current state, identifies gaps, and defines the target architecture for WI-248 (Technitium migration).

Current DNS services found

Pi-hole (caneast-site1-node1 — REDACTED / REDACTED)

Pi-hole v6.4.1 (Core v6.3, FTL v6.4.1) is running as a native service on the RPi4. It is not containerised. Key attributes:

Attribute Value
Version v6.4.1 (behind latest v6.6.1)
Listen address 0.0.0.0:[REDACTED]
Interface binding eth0 (REDACTED)
Listening mode LOCAL
Upstream DNS 9.9.9.9, 149.112.112.112 (Quad9 plain UDP)
DHCP Disabled
Custom DNS records None
peries.ca rewrites None
Web admin http://REDACTED/admin (TLS cert present)
Database size 324 MB (significant query history)

Pi-hole has no peries.ca zone data. It can resolve external names via Quad9 but cannot answer any internal platform hostname queries.

Query volume (live):

Client 24 h queries 7 d queries Client identity
REDACTED 42,404 54,697 caneast-site1-node2 / AdGuard host
REDACTED 6,429 24,444 CanEast AI Node WSL host
REDACTED 1,131 19,922 Unidentified (MAC 5c:c1:d7:8b:3a:ec, Huawei OUI)
127.0.0.1 118 677 caneast-site1-node1 localhost
Total 50,082 99,740

Pi-hole is receiving ~50K queries/day. This is production load. Retiring Pi-hole requires explicit mitigation steps (see Consequences).

caneast-site1-node1 itself does NOT use Pi-hole for DNS — its /etc/resolv.conf is managed by Tailscale and points to 100.100.100.100.

AdGuard Home (caneast-site1-node2 — REDACTED)

AdGuard Home runs as a Docker container (adguard/adguardhome:latest) with host networking on caneast-site1-node2. It is the canonical internal DNS authority.

Attribute Value
Container adguard/adguardhome:latest, host network
Admin http://REDACTED:[REDACTED]
DNS bind 0.0.0.0:[REDACTED]
Upstream DNS Quad9 DoH (dns10.quad9.net), Cloudflare DoH (cloudflare-dns.com)
Bootstrap DNS 9.9.9.10, 149.112.112.10, 2620:fe::[REDACTED]
DHCP Disabled
Filter AdGuard DNS filter (enabled), AdAway (disabled)
Config volume adguard_adguard_conf (named Docker volume)
Per-domain upstreams None

DNS rewrites (11 records — complete list):

Domain Answer Notes
*.peries.ca REDACTED Wildcard catch-all
*.caneast-site1-node3.peries.ca REDACTED Overrides wildcard
grafana-platform.peries.ca REDACTED Explicit override
chat.peries.ca REDACTED Explicit override
openclaw.peries.ca REDACTED Explicit override
openclaw-node3.peries.ca REDACTED Explicit override
cmms.peries.ca REDACTED Explicit override
caneast-site1-node4.peries.ca REDACTED Node record
caneast-site1-node2.home REDACTED .home shortname
caneast-site1-node1.home REDACTED .home shortname (wlan0 IP)
caneast-site1-node3.home REDACTED .home shortname
caneast-site1-node4.home REDACTED .home shortname

Note: caneast-site1-node1.home → REDACTED points to the wlan0 interface, not eth0 (REDACTED). This should be corrected to REDACTED (eth0).

No second AdGuard found. The assumption of "two AdGuard instances" was incorrect. caneast-site1-node1 runs Pi-hole only.

Router (REDACTED — Bell Giga Hub)

The Bell Giga Hub is the active router, DHCP server, and default gateway for the REDACTED/24 LAN. It is NOT OPNsense (OPNsense KVM is work-in-progress per ADR-0004 and WI-248).

DHCP distributes the following DNS servers to all LAN clients:

# IP Identity
Primary REDACTED Bell Giga Hub (self)
Secondary 207.164.234.193 Bell Canada ISP DNS (toroon63dnsvp1.srvr.bell.ca)

The router does not push AdGuard's IP to clients. Nodes that receive DHCP DNS (caneast-site1-node3, caneast-site1-node4) resolve external names through the ISP gateway but cannot resolve peries.ca internal names via the OS resolver.

The Bell Giga Hub acts as a DNS relay/forwarder using Bell's upstream DNS infrastructure. It has no knowledge of peries.ca zone records.

Node DNS resolver summary

Node DNS config Primary DNS peries.ca resolves?
caneast-site1-node1 Tailscale (managed /etc/resolv.conf) 100.100.100.100 (Tailscale) Via Tailscale if configured
caneast-site1-node2 Static (WI-305) REDACTED (AdGuard) then REDACTED (Pi-hole) then 1.1.1.1 Yes (AdGuard primary)
caneast-site1-node3 systemd-resolved, DHCP on lan-bridge REDACTED (ISP gateway) No — NXDOMAIN
caneast-site1-node4 systemd-resolved, DHCP REDACTED (ISP gateway) No — NXDOMAIN
CanEast AI Node WSL WSL-generated REDACTED (WSL internal) Inherited from Windows host

caneast-site1-node3 eno1 has REDACTED and REDACTED configured via systemd-resolved but with "no active scope" — these are not used for resolution by the main lan-bridge interface.

Gap: caneast-site1-node3 and caneast-site1-node4 cannot resolve peries.ca via the system resolver. k3s pods on these nodes depend on the CoreDNS bridge (WI-345) for internal name resolution.

CoreDNS bridge (temporary — WI-345)

The CoreDNS-custom ConfigMap in kube-system forwards peries.ca zone queries to AdGuard (REDACTED:[REDACTED]). This fills the gap for k3s pods that need internal name resolution. It is explicitly temporary — the bridge is to be removed when WI-248 (Technitium) is complete and the router DHCP is updated to distribute Technitium's IP.

Why Pi-hole has production load

caneast-site1-node2's static resolv.conf (installed by WI-305 to bypass Tailscale DNS interference) lists Pi-hole (REDACTED) as the secondary resolver. When AdGuard fails to answer a query — or when AdGuard itself uses the system resolver for its use_private_ptr_resolvers PTR lookups — the queries fall through to Pi-hole at the secondary. The AdGuard Docker container runs with host networking, so all such queries originate from REDACTED.

This creates an unintentional functional dependency: Pi-hole is secondary DNS for AdGuard's host, meaning any AdGuard resolver behaviour that falls through to the system resolver can reach Pi-hole. Since Pi-hole has no peries.ca records, internal name fallback silently fails.

Infisical note

Neither the InfluxDB token nor Grafana admin password is stored in Infisical. These are security gaps independent of DNS (tracked separately).

Decision drivers

Driver Weight Notes
Internal name resolution completeness High All nodes must resolve peries.ca; currently caneast-site1-node3/4 cannot
Single authoritative source of truth High Ad-hoc AdGuard rewrites are unversioned and unaudited
Ad/tracking blocking retained High AdGuard filter must remain active for client protection
DHCP DNS pushed to all nodes High Currently only caneast-site1-node2 uses AdGuard — router DNS gap must close
HA / redundancy Medium Pi-hole secondary is fragile; target state needs proper HA
Prometheus observability Medium DNS metrics should feed the monitoring stack
Migration safety High Production load on Pi-hole means a retire-before-replace would break 42K queries/day

Current state architecture

LAN clients (DHCP)
  └── REDACTED (Bell Giga Hub) — DNS relay → Bell ISP DNS
        ↑ does NOT push AdGuard to clients

caneast-site1-node2 (static resolv.conf / WI-305)
  Primary:   REDACTED (AdGuard Home)
  Secondary: REDACTED (Pi-hole wlan0) ← 42K queries/day
  Fallback:  1.1.1.1

AdGuard Home (caneast-site1-node2:[REDACTED], host-network)
  ├── 11 DNS rewrites (peries.ca zone)
  ├── Upstream: Quad9 DoH + Cloudflare DoH
  └── Filtering: AdGuard DNS filter

Pi-hole (caneast-site1-node1:[REDACTED])
  ├── No peries.ca records
  ├── Upstream: Quad9 plain
  └── Serving: caneast-site1-node2 secondary, CanEast AI Node Windows host, unknown REDACTED

k3s pods (all nodes)
  └── CoreDNS → CoreDNS-custom (peries.ca) → AdGuard REDACTED:[REDACTED]

caneast-site1-node3, caneast-site1-node4 (systemd-resolved / DHCP)
  └── REDACTED (ISP gateway) → Bell ISP DNS
        ↑ cannot resolve peries.ca

Target state architecture

The target state consolidates DNS authority into Technitium and pushes it to all nodes via DHCP. AdGuard is retained as a filtering layer in front of Technitium. Pi-hole is retired once dependencies are cleared.

Bell Giga Hub DHCP
  └── DNS1: REDACTED (AdGuard — filtering layer)
  └── DNS2: REDACTED (Technitium replica — HA secondary)

AdGuard Home (caneast-site1-node2)
  ├── All filtering rules retained
  └── Upstream for internal: Technitium (REDACTED:[REDACTED] → local)
      Upstream for external: Quad9 DoH + Cloudflare DoH (unchanged)

Technitium primary (caneast-site1-node2, port 5380)
  ├── peries.ca zone (authoritative)
  ├── All AdGuard rewrites migrated as DNS records
  ├── Prometheus metrics endpoint
  └── Sync to replica

Technitium replica (caneast-site1-node1, replaces Pi-hole)
  ├── Read-only sync from primary
  └── HA fallback if caneast-site1-node2 is unavailable

caneast-site1-node2 resolv.conf (post-migration)
  Primary:   REDACTED (AdGuard — filtering, forwards to Technitium)
  Secondary: REDACTED (Technitium replica direct)
  Remove:    REDACTED (Pi-hole — retired)

k3s CoreDNS bridge
  └── Remains peries.ca → AdGuard (REDACTED) — update when DHCP lands
      → Remove bridge once all nodes receive AdGuard via DHCP

caneast-site1-node3, caneast-site1-node4 (post DHCP update)
  └── AdGuard (REDACTED) → Technitium → peries.ca resolved

Options considered

Option A: Replace Pi-hole + AdGuard with Technitium only

Technitium handles both filtering and authority. AdGuard is decommissioned. Technitium's built-in DNS blocking lists replace AdGuard's filter.

Pros: Single service, single config, simpler topology.

Cons: AdGuard's mature filter lists and per-client rules are harder to replicate in Technitium's blocklist model. AdGuard provides a richer filtering UX. Risk: Technitium DNS filter quality is lower than AdGuard's for ad/tracking blocking. Decommissioning AdGuard removes a proven tool.

Option B: Technitium as authority layer behind AdGuard (scope split)

AdGuard remains the client-facing DNS (filtering, DoH upstream). Technitium provides the peries.ca authority zone. AdGuard forwards internal zone queries to Technitium. External queries continue via AdGuard's DoH upstreams.

Pros: Clean separation of concerns — AdGuard = filter/resolver, Technitium = zone authority. Preserves AdGuard's filtering capability. Technitium provides versioned zone management with built-in Prometheus metrics. HA is handled by Technitium's primary/replica replication.

Cons: Two services to operate. Per-domain upstream in AdGuard must be configured correctly; misconfiguration silently falls through to DoH upstream which would resolve peries.ca externally (Cloudflare responds NXDOMAIN for internal names — safe but slower).

Option C: AdGuard custom DNS rewrites (status quo, expanded)

Continue using AdGuard rewrites for all internal records. No Technitium. Fix the current gaps: update DHCP to push AdGuard to all nodes, remove Pi-hole, add Prometheus via AdGuard's built-in metrics endpoint.

Pros: No new services. Familiar tooling.

Cons: AdGuard rewrites are not versioned zone files — they cannot be audited via git diff, exported easily, or managed via standard DNS tooling. No HA (single AdGuard instance; Pi-hole has no records so is not a real HA partner). No SOA/NS/MX record support (limits future peries.ca zone management). Blocks future Technitium adoption (WI-248 scope would shrink to nothing).

Decision

Option B: Technitium as authority layer behind AdGuard.

Technitium handles the peries.ca zone (all current AdGuard rewrites migrated as proper A records). AdGuard remains client-facing for filtering and upstream DoH. AdGuard adds a per-domain upstream rule: [/peries.ca/][/.home/] 127.0.0.1:5380 (or the Technitium service port).

Pi-hole is to be retired. This is gated on: 1. Removing REDACTED from caneast-site1-node2's resolv.conf secondary 2. Identifying and reconfiguring REDACTED (CanEast AI Node Windows) and REDACTED (unidentified client) to use AdGuard instead 3. Deploying Technitium replica to caneast-site1-node1 before decommissioning Pi-hole

WI-248 scope is unchanged — migration phases below replace the existing scope description.

Rationale

Pi-hole is not a viable HA partner because it has no peries.ca records. Its presence as secondary DNS on caneast-site1-node2 provides no internal name resolution fallback. The 42K queries/day it receives from AdGuard's host are an unintentional side effect of use_private_ptr_resolvers and secondary resolver fallback — not deliberate production traffic. The CanEast AI Node Windows host and unknown REDACTED appear to have been manually configured to use Pi-hole.

Technitium (WI-248) with primary/replica replication on caneast-site1-node2/caneast-site1-node1 provides genuine HA for the peries.ca zone. AdGuard's proven filter coverage is retained. This is the cleanest path to resolving all gaps without introducing a new tool for filtering (Option A) or deferring HA forever (Option C).

Migration phases (high level — no dates)

These phases describe the implementation sequence for WI-248. Each phase is a prerequisite for the next. Do not execute migration this sprint — this ADR documents the plan only.

Phase 1 — Pre-migration (prerequisite hygiene)

  • Identify REDACTED (MAC 5c:c1:d7:8b:3a:ec) — determine owner and reconfigure to use AdGuard
  • Confirm CanEast AI Node Windows host DNS settings — update to AdGuard (REDACTED) as primary
  • Fix caneast-site1-node1.home rewrite in AdGuard: change REDACTED → REDACTED
  • Document all 11 AdGuard rewrites as the migration source of truth

Phase 2 — Deploy Technitium primary on caneast-site1-node2

  • Deploy technitium/technitium-dns-server container on caneast-site1-node2 (port 5380 admin, port 53 DNS — AdGuard will route to it, not expose to LAN)
  • Create peries.ca zone with all 11 records migrated from AdGuard rewrites plus any missing records (SOA, NS)
  • Configure AdGuard per-domain upstream: [/peries.ca/][/.home/] 127.0.0.1:5380
  • Validate: all AdGuard rewrites resolve through Technitium
  • Add Technitium Prometheus scrape to telegraf/Grafana

Phase 3 — Deploy Technitium replica on caneast-site1-node1

  • Deploy Technitium replica on caneast-site1-node1 (replaces Pi-hole)
  • Configure primary→replica zone sync
  • Validate: replica serves peries.ca read-only

Phase 4 — Update resolv.conf and DHCP

  • Update caneast-site1-node2 resolv.conf: replace secondary REDACTED (Pi-hole) with REDACTED (Technitium replica)
  • Update Bell Giga Hub DHCP: DNS1=REDACTED (AdGuard), DNS2=REDACTED (Technitium replica)
  • Validate: caneast-site1-node3, caneast-site1-node4 resolve peries.ca via DHCP-received DNS

Phase 5 — Retire Pi-hole

  • Confirm zero traffic to REDACTED:[REDACTED] over 24h
  • Stop and disable Pi-hole service on caneast-site1-node1
  • Validate: Technitium replica still serves DNS on caneast-site1-node1

Phase 6 — Remove CoreDNS bridge

  • Confirm all nodes resolve peries.ca via system resolver (DHCP DNS)
  • Remove CoreDNS-custom peries.ca stanza from kube-system
  • Validate: k3s pods resolve internal names via CoreDNS default (which will forward to AdGuard via DHCP-configured nameserver on the node)
  • Update reference docs

Consequences

Positive

  • All nodes resolve peries.ca (DHCP DNS closes the caneast-site1-node3/4 gap)
  • peries.ca zone is versioned, manageable, and HA (primary + replica)
  • Pi-hole retired cleanly — no surprise secondary dependency
  • Prometheus DNS metrics feed the monitoring stack
  • AdGuard filtering retained with no regression
  • CoreDNS bridge removed (eliminates technical debt from WI-345)

Negative / risks

  • Pi-hole retire is gated on identifying REDACTED (unknown client) and confirming CanEast AI Node Windows DNS config. Neither is blocking for ADR acceptance, but both are pre-conditions for Phase 4/5.
  • Two services to operate (AdGuard + Technitium). Offset by clear scope split.
  • AdGuard per-domain upstream config is new config surface not currently managed via IaC. Should be captured in Ansible or docker-compose template before migration.
  • Bell Giga Hub DHCP change requires GUI access (no API). Manual step.
  • caneast-site1-node1.home rewrite bug (points to wlan0 not eth0) must be fixed in Phase 1 before migration to avoid carrying the error into Technitium.

Neutral

  • The infisical.peries.ca DNS rewrite (pending migration to NRP proxy on caneast-site1-node2) will need to be added to Technitium zone in Phase 2.
  • archon-docs reference/network.md DNS table must be updated post-migration.
  • Tailscale DNS on caneast-site1-node1 (100.100.100.100) is unaffected — Technitium replica will coexist with Tailscale on caneast-site1-node1 using different interfaces.

Stop conditions (must be surfaced before Phase 4)

  1. Pi-hole REDACTED client unidentified — flag before retiring Pi-hole
  2. CanEast AI Node Windows host DNS config — confirm before retiring Pi-hole
  3. caneast-site1-node1.home rewrite bug — fix in Phase 1 before Technitium zone creation

References

  • ADR-0004 — OPNsense KVM (OPNsense will eventually handle DHCP; router DHCP is interim)
  • WI-248 — Technitium DNS migration (implementation; this ADR supersedes WI-248's scope description)
  • WI-345 — CoreDNS peries.ca zone forward bridge (temporary; remove in Phase 6)
  • WI-305 — Tailscale DNS on Docker hosts (source of caneast-site1-node2 static resolv.conf)
  • WI-363 — DNS Architecture & Migration Planning (Feature parent)
  • WI-364 — ADR-0041 DNS architecture discovery (this ADR's work item)
  • Pi-hole v6.4.1 running on caneast-site1-node1 (discovered 2026-04-27)
  • AdGuard Home running on caneast-site1-node2 Docker host-network (confirmed 2026-04-27)