AdGuard Home: DNS Filtering as Infrastructure, Not Just a Privacy Tool

AdGuard Home: DNS Filtering as Infrastructure, Not Just a Privacy Tool

AdGuard Home gets introduced to most homelab builders the same way: as a Pi-hole replacement. Better interface, built-in DoH, easier to update. A cleaner implementation of the same idea — filter DNS queries to block ads and trackers at the network level. The framing is a privacy tool with better ergonomics.

That framing is accurate as far as it goes. It also undersells what you're actually deploying by a significant margin.

When a filter becomes a control plane

The moment you configure AdGuard Home as the DNS server for every device on your network — not just a subset, but every client — the nature of the service changes. You're no longer running a filter in front of your DNS resolver. You're running the DNS resolver.

That distinction matters operationally. Conditional rewrites let you define internal hostnames and route them to specific IPs — which means your homelab services can have real names rather than IP addresses held only in the memory of whoever maintains the infrastructure. Per-client filtering means different DNS policies can apply to different devices: stricter filtering for children's devices, full query access for servers, content policies for IoT. Query logging becomes an audit trail — who resolved what, when, from which device.

None of these are privacy features. They're infrastructure management capabilities. The framing of AdGuard as a "privacy tool" describes what it does for a single device trying to reduce data collection. It doesn't describe what it becomes when positioned as the DNS layer for an entire network: a policy enforcement point for every hostname lookup that crosses the wire.

The load-bearing problem

DNS resolves before anything else can initialize. Not before most things — before everything. When you open a browser, connect to a service, pull a container image, or run a health check, the first step is almost always a DNS lookup. This is so fundamental that infrastructure designers have built multiple layers of redundancy around it: systems cache DNS aggressively, clients retry on failure, container runtimes maintain their own internal resolvers specifically to avoid depending on external DNS during startup.

That last detail is the one that shapes the homelab AdGuard conversation. Docker maintains its own internal DNS resolver at 127.0.0.11. When a container needs to resolve a hostname, Docker handles that request internally — not because external DNS is unreliable, but because containers can't safely depend on external DNS during initialization. The host might not have its resolver configured yet. The network stack might not be fully up. So Docker handles it itself.

The consequence is structural: when AdGuard runs inside Docker, your DNS resolver sits downstream of a container runtime that has its own DNS layer specifically because DNS might not be available. For LAN clients and the host itself, this works fine — their traffic reaches AdGuard at its published port. For containers on the same host, it often doesn't, because those containers are resolving through 127.0.0.11, not through your AdGuard instance. The conditional rewrites and per-client policy you configured don't apply to the containers sharing the same Docker host. You've built a two-class DNS environment without intending to.

The container runtime exception

The "containerize everything" model has real operational appeal. One deployment pattern, one backup strategy, one way to update and rebuild. It handles the vast majority of homelab services cleanly. It has a category of exceptions, and DNS is the clearest one.

Load-bearing infrastructure — services that other services depend on before they can initialize — can't sit at the same layer as the services it supports. It has to be below that layer, or at minimum beside it, outside the dependency chain. DNS meets this definition unambiguously. So does NTP if you have services that require synchronized clocks to validate certificates or form consensus. So does any secret store if containers pull credentials during startup. These services have a dependency inversion problem: the thing they run inside depends on them first.

This isn't a Docker-specific limitation. It's a logical constraint about what can safely depend on what. Any service that must be available before a runtime initializes cannot meaningfully run inside that runtime. Docker, to its credit, solves this for its own needs by running its own resolver. The gap it creates is that AdGuard, if you want it to be the DNS resolver for the host and for containers alike, can't be treated as a normal user workload in a Compose stack.

The practical paths are running AdGuard on bare metal, in an LXC container, or in Docker with network_mode: host — which lets it operate at the system level rather than inside Docker's bridge network. None of these feel as clean as a standard Compose deployment. That friction is probably why the Docker-inside-bridge pattern is so common despite the split-DNS problem it creates. The wrong answer is easy; the right one requires a step back.

Where trust lives in the DNS chain

The upstream resolver question tends to get less attention than the deployment question. It's worth more. Every query your AdGuard instance forwards to an upstream resolver is a record: which domains were resolved, when, from which source IP. When you run AdGuard as the DNS control plane for an entire network rather than just a personal device, the upstream choice determines where your whole network's DNS history ends up.

Public resolvers like Cloudflare's 1.1.1.1 have documented privacy commitments and are audited against them. The queries still leave your network. For many homelab setups, this is an acceptable tradeoff — fast, maintained, no additional complexity. For setups where DNS-as-infrastructure is the explicit goal, it creates a philosophical inconsistency: you've taken ownership of DNS policy at the network level but outsourced the underlying resolution to a third party.

Running a local recursive resolver like Unbound changes the model. Unbound resolves directly from root servers — your homelab becomes the authoritative resolver for its own network, with no upstream logging your queries. You add a dependency in the DNS path and some latency on cold lookups. What you get is a trust model consistent with the infrastructure framing: if the goal is to own DNS, own the whole chain.

Neither answer is universally correct. The right upstream choice depends on the threat model you're operating under, the complexity you're willing to maintain, and how much the DNS privacy posture actually matters in your specific setup. What changes when you frame it as a trust model decision rather than a settings panel choice is that the tradeoffs become visible. You can make the call deliberately instead of accepting the default.

What the homelab is still working out

The separation of DNS from the container runtime is not a new idea in production infrastructure. Kubernetes handles this explicitly — CoreDNS runs as a cluster system component, not as a user workload. It exists outside the deployment model that user services operate within, precisely because the cluster's DNS can't depend on the cluster's runtime. The pattern is the same one that creates the homelab split-DNS problem; the difference is that Kubernetes enforced the architecture deliberately from the start.

The homelab absorbs production patterns with a lag, often after hitting the edge cases that made those patterns necessary. The DNS conversation in homelab communities — Docker vs. LXC, network_mode: host, forwarding vs. recursive — is the community working toward the same architectural conclusion through trial and error rather than upfront design.

What remains genuinely unresolved is what DNS architecture looks like as homelab setups grow beyond a single node. Multi-host deployments need DNS that spans nodes. High-availability setups need redundant resolvers. AdGuard Home doesn't natively support the HA patterns that production DNS infrastructure assumes. At the point where a homelab has grown to resemble a small production environment, the single AdGuard instance starts showing its edges — not because it's the wrong tool, but because the architecture around it hasn't kept pace with the scale.

Whether that matters depends entirely on what the homelab is actually doing. For most single-node setups, the split-DNS problem is the real challenge, and solving it is sufficient. For setups that have crossed into multi-node territory, the question of DNS authority — where it lives, who owns it, what happens when it fails — is one worth thinking through before it becomes urgent.

Ryan Mckernan
Author

Ryan Mckernan

IT service manager & infrastructure architect turning real-world IT messes into practical, documented fixes. I build systems, streamline ops, and share field notes at Painfully Useful—tested, refined, and repeatable.