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:
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¶
- Nextcloud — the single-domain app this serves
- Ad Blocking · Docker Networking · Tailscale
- Blog post: Context-aware DNS with Nextcloud