Bloody Tears of DNS Madness

April, 2025
Using dnsmasq for local development with Docker and Kubernetes

Why does a man climb a mountain? Because it’s there.

Docker is great. Kubernetes is great. Communication between the two? Not so much. This post will explain how to easily solve this problem using dnsmasq. Your skepticism is noted.

Run dnsmasq in Docker

dnsmasq is awesome. You can use it to run a local DNS server while developing your applications.

log-queries
no-resolv
cache-size=1000

server=/.docker/127.0.0.11 # Docker internal DNS
server=/.k8s.local/10.43.0.10 # CoreDNS (mDNS uses .local, this must be accounted for)
server=/.cluster.local/10.43.0.10 # CoreDNS (mDNS uses .local, this must be accounted for)
server=1.1.1.1 # Cloudflare
server=8.8.4.4 # Google

address=/.test/{HOST_IP} # localhost IP

The config above will cause any domains ending in .test to resolve to the IP of your local computer. Any domain matching the server rules will be handled by the specified DNS server.

docker network create --subnet=172.20.0.0/16 dns
docker run -d --restart unless-stopped --name dnsmasq -v \
./dnsmasq.conf:/etc/dnsmasq.conf --network dns --ip 172.20.0.1 <the dnsmasq
image> \
dnsmasq -k --log-queries --log-facility=-

Configure Host DNS

The Docker container for this config should not be bound to a host port. Instead, update your Linux host (I use Arch, by the way) /etc/systemd/resolved.conf.

[Resolve]
DNS={DNSMASQ_IP}
# By default, systemd-resolved treats the .local domain as a special-use
# domain for Multicast DNS (mDNS) and doesn't forward these queries to your
# configured DNS servers. `Domains=~local` overrides that behavior
Domains=~local
# Disable MulticastDNS (mDNS)
MulticastDNS=no
# Listen on 127.0.0.53 (where /etc/resolv.conf usually points)
DNSStubListener=yes

Then run sudo systemctl restart systemd-resolved.

Double-check that /etc/resolv.conf is configured with nameserver 127.0.0.53. The IP 127.0.0.53 is where systemd-resolved listens for DNS queries.

Now run dig +short foo.test and you should see {HOST_IP} returned. If not, try dig @{DNSMASQ_IP} foo.test to verify that dnsmasq is properly configured.

Now run:

docker run -d --rm --name nginx --network dns --network-alias nginx.docker

And curl nginx.docker

Note:

Docker will not resolve subdomains for nginx.docker because it only knows how to resolve Docker hostnames. So you’ll have to add more network-aliases or update dnsmasq with address=/.nginx/{NGINX_IP} if you need that.

Configure Kubernetes DNS

This should be the easy part, but that will depend on your Kubernetes cluster. If you’re using CoreDNS, simply update the config with forward . {DNSMASQ_IP} and restart CoreDNS.

Now test everything with:

kubectl run curl --image=curl --restart=Never -- sleep infinity
kubectl exec curl -- curl nginx.docker

Check out this repo for a no-guarantees example of this configuration.