Skip to content

Ad Blocking + Local DNS (AdGuard Home / Pi-hole)

TL;DR / when to use this: Run AdGuard Home or Pi-hole as the home-network DNS: block ads and trackers network-wide, and get your own DNS server in the process. Two deployment styles — macvlan (each DNS server gets its own LAN IP, both can run at once) or bridge (one at a time, the host itself becomes the DNS server). The networking trade-offs are in Docker Networking.

Option A — macvlan (both servers, own IPs)

On a macvlan network, containers don't need port mappings — they're exposed directly on the LAN with static IPs:

services:
  adguardhome:
    image: adguard/adguardhome
    container_name: adguardhome
    restart: unless-stopped
    volumes:
      - ./work:/opt/adguardhome/work
      - ./conf:/opt/adguardhome/conf
    networks:
      adguard_macvlan_network:
        ipv4_address: 192.168.4.100

  pihole:
    image: pihole/pihole:latest
    container_name: pihole
    environment:
      TZ: 'America/Denver'
      FTLCONF_webserver_api_password: 'correct horse battery staple'
      FTLCONF_dns_listeningMode: 'all'
    volumes:
      - './etc-pihole:/etc/pihole'
    cap_add:
      - SYS_NICE
    restart: unless-stopped
    networks:
      adguard_macvlan_network:
        ipv4_address: 192.168.4.101

networks:
  adguard_macvlan_network:
    driver: macvlan
    driver_opts:
      parent: eno1            # your host interface (find it with `ip address`)
    ipam:
      config:
        - subnet: 192.168.4.0/24
          gateway: 192.168.4.1   # your router

Pick IPs the router isn't already using.

Host ↔ macvlan communication

By default the host cannot talk to macvlan containers (LAN clients can). That breaks the host using its own DNS server, so create a bridge link:

sudo ip link add macvlan0 link eno1 type macvlan mode bridge
sudo ip addr add 192.168.4.254/24 dev macvlan0
sudo ip link set macvlan0 up
sudo ip route add 192.168.4.100 dev macvlan0
sudo ip route add 192.168.4.101 dev macvlan0

These are ephemeral — persist them with a oneshot systemd service:

# /usr/local/sbin/setup-macvlan.sh (chmod +x)
ip link add macvlan0 link eno1 type macvlan mode bridge
ip addr add 192.168.4.254/24 dev macvlan0
ip link set macvlan0 up
ip route add 192.168.4.100 dev macvlan0
ip route add 192.168.4.101 dev macvlan0
# /etc/systemd/system/macvlan.service
[Unit]
Description=Setup macvlan0 interface
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/setup-macvlan.sh
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now macvlan.service

Option B — bridge (one server, host becomes DNS)

Simpler, no link gymnastics, but only one DNS server can bind port 53. First disable systemd-resolved's stub listener so the container can own port 53:

sudo ss -tulnp | grep :53          # confirm what owns it
sudo nano /etc/systemd/resolved.conf
DNSStubListener=no
DNS=1.1.1.3
FallbackDNS=1.0.0.3
sudo systemctl daemon-reload
sudo systemctl restart systemd-resolved

Then run Pi-hole (or AdGuard Home) on the default bridge network with 53:53 mapped and FTLCONF_dns_listeningMode: 'all' (required for Pi-hole on bridge).

Client notes

  • Windows: set DNS to Automatic (Settings → Network & internet → DNS server assignment) so the local domain resolves.
  • Android: set Private DNS to Automatic for local resolution.

See also