Context-Aware DNS¶
Some apps accept exactly one domain, and Nextcloud is the one that pushed me to do this. 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. To do that you need two DNS servers on the same box, which means 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