AdGuard Home + Unbound¶
DNS filtering and recursive resolution on infra-services. Replaces PiHole
(LXC 104 blocktopus, 192.168.6.80).
| Key | Value |
|---|---|
| Compose | services/adguard/compose.yml |
| Admin UI | https://adguard.infra.realemail.app |
| DNS | 192.168.6.17:53 (UDP/TCP) |
| Upstream | Unbound container on compose network |
| Backup tier | 2 |
Full operator README (import script, upstream UI fields, compose notes):
services/adguard/README.md
in the repo.
Architecture¶
Clients → AdGuard Home (:53) → Unbound (:53) → root servers
│
└─ DNS rewrites (*.lab.local, *.infra.realemail.app)
AdGuard handles filtering and local rewrites; Unbound recurses so the lab does not depend on a public resolver.
Cutover status (2026-06-19)¶
| Item | Status |
|---|---|
| Stack deployed on infra-services | Done |
Traefik UI + wildcard *.infra.realemail.app rewrite |
Done |
| Inventory rewrites imported (~29 + infra wildcard) | Done |
| UFW allow LAN → :53 on infra-services | Done |
| Tailscale prefer-main fix (Servers VLAN same-L2 DNS) | Done (Ansible + systemd) |
UDM WAN DNS → 192.168.6.17 |
Owner — finish cutover |
All VLAN DHCP DNS → 192.168.6.17 |
Owner — in progress |
| IPv6 DNS on UDM | Deferred (AdGuard IPv4 only today) |
| PiHole parallel soak + LXC 104 decom | Pending |
See Phase 7 Owner Actions — §8.
Servers VLAN caveat (Tailscale)¶
infra-services advertises 192.168.6.0/24 on Tailscale. Without a
prefer-main ip rule, DNS replies to same-subnet clients (e.g. saltierpoop)
leave via tailscale0 instead of eth0 and clients time out.
Managed by Ansible (tailscale_prefer_main_routes) and
tailscale-local-subnet-routes.service on the host.
Verify¶
dig @192.168.6.17 google.com +short
dig @192.168.6.17 infra-services.lab.local +short
dig @192.168.6.17 adguard.infra.realemail.app +short
From a Servers VLAN host, all three must succeed before decommissioning PiHole.