Skip to content

Context-Aware DNS

TL;DR / when to use this: Some apps (Nextcloud chief among them) accept exactly one domain. Context-aware DNS lets that single domain resolve to different IPs depending on where the client is: the local LAN IP on the home network, the Tailscale IP on the tailnet, and the public/Cloudflare IP elsewhere. The trick is running two DNS servers on the same box — which requires macvlan so each can own port 53 with its own LAN IP.

Why two DNS servers

  • AdGuard Home serves the local network — resolves nextcloud.example.com → the server's LAN IP.
  • Pi-hole serves the tailnet — resolves the same name → the server's Tailscale IP.

Both need port 53, which the host already occupies, so both run on a macvlan network with their own IPs. A Tailscale sidecar advertises the Pi-hole macvlan IP to the tailnet.

services:
  adguardhome:
    image: adguard/adguardhome
    networks:
      macvlan_net:
        ipv4_address: 192.168.4.100

  pihole:
    image: pihole/pihole:latest
    environment:
      FTLCONF_dns_listeningMode: 'all'
    networks:
      macvlan_net:
        ipv4_address: 192.168.4.101

  tailscale:
    image: tailscale/tailscale:latest
    environment:
      TS_AUTHKEY: ${tailscale_auth_key}
      TS_ACCEPT_DNS: "true"
      TS_ROUTES: 192.168.4.101/32     # advertise Pi-hole to the tailnet
    networks:
      macvlan_net:
        ipv4_address: 192.168.4.103
    cap_add: [NET_ADMIN, SYS_MODULE]

networks:
  macvlan_net:
    driver: macvlan
    driver_opts:
      parent: eno1
    ipam:
      config:
        - subnet: 192.168.4.0/24
          gateway: 192.168.4.1

Macvlan basics, the host↔container link, and the systemd persistence service are covered in Docker Networking and Ad Blocking.

DNS records

AdGuard Home (Filters → DNS Rewrites):

Domain Answer
nextcloud.example.com 192.168.4.142 (LAN IP)
collabora.example.com LAN IP
onlyoffice.example.com LAN IP

Pi-hole (same records, tailnet answers):

Domain Answer
nextcloud.example.com 100.x.x.x (Tailscale IP)
collabora.example.com Tailscale IP
onlyoffice.example.com Tailscale IP

In the Tailscale admin console, set the tailnet nameserver to Pi-hole (192.168.4.101) with Override DNS servers, and make sure Windows/mobile clients use Tailscale DNS in the app.

Client + container notes

  • Windows: DNS server assignment must be Automatic for the domain to resolve locally.
  • Android: Private DNS must be Automatic for local resolution.
  • App containers (Nextcloud, Collabora) must point at the local DNS server so they can resolve the domain themselves:
    dns:
      - 192.168.4.100

Collabora runs with SSL termination handled by Nginx (--o:ssl.enable=false --o:ssl.termination=true), and certs for collabora.example.com / onlyoffice.example.com come from the certbot container via the Cloudflare DNS plugin.

Why this matters

One canonical domain, three contexts — local clients hit the LAN directly, tailnet clients route through Tailscale, and public clients go through Cloudflare. No app reconfiguration, no second domain, no split-brain. Nextcloud is the consumer example; the pattern generalizes to any single-domain app.

See also