[Homelab] AdGuard Home on Rocky Linux with Quadlet: DNS as a Network Control Plane

5 min read Original article ↗

DNS as a Network Control Plane #

Most homelabs treat DNS as a background utility, something that just works and gets forgotten. That’s a missed opportunity. DNS is the single most powerful policy enforcement point on your network. Every device, every connection, every hostname lookup flows through it.

Running AdGuard Home in a Quadlet-managed container on Rocky Linux gives you something more than ad-blocking. You get a declarative, boot-persistent DNS control plane with per-device policy enforcement, encrypted upstream resolution, and full query visibility all without touching individual clients.


Target Architecture #

flowchart TD Clients[LAN / WiFi Clients] Router[FRITZ!Box DHCP] AGH[AdGuard Home
Quadlet Container] Upstream[Encrypted Upstream DNS
DoH / DoT] Clients --> Router Router -->|DNS handed to clients| AGH AGH --> Upstream

The router keeps doing what it does best: handing out DHCP leases. We just redirect the DNS pointer to AdGuard Home. No per-device configuration, no agent installs.


Host Preparation #

Start with a minimal Rocky Linux host and install Podman:

dnf install -y podman

mkdir -p /opt/adguard/work
mkdir -p /opt/adguard/conf

The two directories will hold AdGuard’s runtime state and configuration, mounted into the container as persistent volumes.


Quadlet Definition #

Quadlet turns a simple .container file into a fully managed systemd unit. Drop this at /etc/containers/systemd/adguardhome.container:

[Unit]
Description=AdGuard Home

[Container]
Image=docker.io/adguard/adguardhome:latest
AutoUpdate=registry

PublishPort=53:53/tcp
PublishPort=53:53/udp
PublishPort=3000:3000/tcp

Volume=/opt/adguard/work:/opt/adguardhome/work
Volume=/opt/adguard/conf:/opt/adguardhome/conf

[Install]
WantedBy=multi-user.target

AutoUpdate=registry means Podman’s auto-update service will pull new image versions automatically — no manual intervention required.

Enable and start it:

systemctl daemon-reload
systemctl enable --now adguardhome

Initial Setup #

On first boot, AdGuard Home serves its setup wizard on port 3000:

http://<server-ip>:3000

Complete the wizard to set credentials and choose your network interfaces. After that, the UI shifts to the standard port:

http://<server-ip>

Router Integration #

The cleanest approach is to leave DHCP entirely on the router and change only the advertised DNS server. This keeps lease management stable and limits the blast radius if something goes wrong with the container.

In the router’s home network settings, point the local DNS server field at AdGuard Home’s IP. Force clients to renew their DHCP leases to pick up the change immediately.


Encrypted Upstream DNS #

At homelab scale, running a full recursive resolver adds complexity with minimal benefit. Using DNS-over-HTTPS upstreams is simpler and still keeps your queries encrypted in transit:

https://dns.cloudflare.com/dns-query
https://dns.quad9.net/dns-query

Configure these in AdGuard Home’s upstream DNS settings. You can add multiple providers for redundancy and let AdGuard select the fastest one automatically.


Policy Enforcement #

This is where DNS as a control plane pays off. AdGuard Home lets you define client-specific filtering profiles, so different devices on the same network can have different rules without any changes to the devices themselves.

Some practical configurations:

Per-client control: apply parental filtering to kids’ devices, stricter blocking to IoT devices, and permissive rules to your workstation, all from one place.

DNS rewrites: map internal hostnames to local IPs so you can reach services by name rather than address:

grafana.lab → 192.168.178.50
jenkins.lab → 192.168.178.60

This gives you a lightweight internal service directory without running a full DNS zone.


Observability #

AdGuard Home’s query log is a lightweight but effective tool for understanding what’s happening on your network. You can see which clients are making requests, which domains are being blocked, and spot unusual traffic patterns. For most homelabs this is sufficient. If you need deeper metrics, the query log can feed into an external observability stack.


Troubleshooting #

Port 53 already in use

Something on the host is already listening on port 53, commonly systemd-resolved. Check with:

Disable or reconfigure the conflicting service before starting the container.

Service not starting after reboot

The unit was started but not enabled. Fix it with:

systemctl enable adguardhome

Clients not using AdGuard

They’re still receiving the router’s own IP as their DNS server via DHCP. Verify the router DNS setting was saved correctly and that clients have renewed their leases.

Apple devices not using AdGuard

Make sure to turn off the iCloud relay settings on your apple device when on the home network.


Results #

After setup, the network gains network-wide ad and tracker blocking, encrypted upstream resolution, per-client policy control, and a central query log, all without touching a single client device. The container starts automatically on boot, updates itself, and is fully described by a single .container file under version control.


Lessons Learned #

DNS changes are fundamentally DHCP changes. If clients aren’t using AdGuard, the problem is almost always in how DNS is being advertised, not in the container itself. Keeping the router as the DHCP authority, and pointing it at AdGuard Home, is more resilient than trying to replace DHCP as well.

Start simple. A single upstream provider with default blocklists is a better starting point than an elaborate configuration you’ll have to debug later. Quadlet is particularly well-suited to infrastructure services like this: it’s declarative, integrates natively with systemd, and handles the container lifecycle without needing a compose file or a daemon.

Treat DNS as a platform component. The investment in standing it up correctly pays dividends every time you add a device, a new service, or need to understand what’s happening on your network.