Skip to content

dnsmasq — names on your own network

dnsmasq answers names on your own network: wildcard zones that point a whole subdomain at one address, static records for machines that have no DNS entry, and a cache in front of an upstream resolver. One container.

Last run on a host 8 Nov 2026

Pinned at 2.93 (released 20 Aug 2026) · the newest upstream release

Needs: a host where port 53 is free, and — if it should answer over a VPN — that interface present.

This stack sits outside the usual pattern in two ways. It uses host networking, because it binds directly to the interfaces it serves. And it is not behind Traefik: it is the name layer, so nothing routes to it by hostname.

From core/dnsmasq/:

  1. Copy the environment file:

    Terminal window
    cp .env.example .env

    Set the interfaces it listens on — lo and the VPN interface are the shipped defaults — the upstream resolvers, and the wildcard zones:

    DNS_INTERFACE_1=lo
    DNS_INTERFACE_2=tailscale0
    DNS_WILDCARD_1_DOMAIN=test.lab.example.com
    DNS_WILDCARD_1_IP=100.100.100.10

    It listens on the named interfaces only, not on everything the host has.

  2. Add static records, if any, to config/records.hosts:

    100.100.100.10 nas.lab.example.com
    100.100.100.20 printer.lab.example.com
  3. Render the configuration. The container mounts the result read-only, and editing config/dnsmasq.conf directly is overwritten by the next render:

    Terminal window
    ./ops/scripts/render.sh
  4. Start it:

    Terminal window
    docker compose up -d
    docker compose logs -f
Terminal window
nslookup anything.test.lab.example.com 127.0.0.1 # the wildcard answers
nslookup nas.lab.example.com 127.0.0.1 # a static record
nslookup example.com 127.0.0.1 # forwarding upstream works

All three matter separately: a wildcard can answer while forwarding is broken, and the reverse.

Which change needs which action:

Changed Apply with
config/records.hosts docker exec dnsmasq kill -HUP 1 — re-reads records and clears the cache, no downtime
wildcard zones, upstream resolvers, interfaces in .env ./ops/scripts/render.sh then docker compose restart

Wildcards and upstream forwarding cover AAAA records as well. A machine with both gets two lines in config/records.hosts, one per family:

fd7a:115c:a1e0::10 nas.lab.example.com
100.100.100.10 nas.lab.example.com

The rendered configuration comes from .env and the template, and config/records.hosts is the only file written by hand. Both belong in version control rather than in a backup archive; restoring means checking out the repository, running the render script and starting the container.

  1. Raise APP_TAG in .env.

  2. Terminal window
    docker compose pull && docker compose up -d
  3. Re-run the three checks from Verify. A configuration directive removed upstream shows up as a container that starts and answers nothing.

Nothing answers on the VPN address. The interface is not in the DNS_INTERFACE_* list, or it did not exist when the container started.

A record change has no effect. records.hosts needs the reload signal; .env needs a re-render and a restart. The two are not interchangeable.

Port 53 is already in use. A resolver on the host holds it — systemd-resolved is the usual one. It has to release the port before this container can bind it.