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 at static compose IP 172.21.0.3 |
| 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, *.realemail.app)
AdGuard handles filtering and local rewrites; Unbound recurses so the lab does not depend on a public resolver.
Do not use dynamic Docker IP discovery for the upstream
The 2026-07-08 infra-services reboot reassigned 172.21.0.2 to AdGuard
while AdGuard still forwarded to that old Unbound IP, creating a DNS
self-loop. The compose stack now reserves 172.21.0.2 for AdGuard and
172.21.0.3 for Unbound; the live AdGuard upstream should stay
172.21.0.3.
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 |
Done |
All VLAN DHCP DNS → 192.168.6.17 |
Done |
| IPv6 DNS on UDM | Deferred (AdGuard IPv4 only today) |
| PiHole parallel soak + LXC 104 decom | Done (2026-06-17) |
See Phase 7 Owner Actions — decommission PiHole.
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
cd /opt/homelab/services/adguard && bash scripts/smoke-test.sh
From a Servers VLAN host, all three must succeed before relying on AdGuard alone.
Proxmox discovery LXCs¶
LXCs without an explicit Proxmox -nameserver inherit prox resolvers
(often Tailscale MagicDNS 100.100.100.100). Non-Tailscale guests cannot use
that and will fail DNS (apt update, registry pulls). On prox:
Example: harbor-registry LXC 119 — harbor-registry.
Saltbox hostnames (*.realemail.app)¶
Same cutover gap as infra: Saltbox Traefik on saltierpoop (192.168.6.243)
serves homeass.realemail.app, plex.realemail.app, etc. Without a rewrite,
AdGuard returns Cloudflare public IPs and internal browsers miss local Traefik.
| Domain | Answer |
|---|---|
*.realemail.app |
192.168.6.243 |
Defined in services/adguard/saltbox-dns-rewrites.yaml; import via
./import-rewrites.sh on infra-services (see repo README).