I’ve been doing increasingly more stuff with docker these days, and I’ve bumped into an issue: all my systems use a local DNS resolver, one way or another: as I’m roaming and have some VPNs with split-horizon DNS, my laptop uses dnsmasq as configured by NetworkManager; as I want DNSSEC validation, my servers use a local unbound instance. (I’d prefer to use unbound everywhere, but I roam on some networks where the only DNS recursors that aren’t firewalled don’t support DNSSEC, and unbound is pretty bad at handling that unfortunately)
This is all fine and dandy, if that’s the sort of thing you like (I don’t kinkshame), but if you don’t trust the owners of those DNS recursors, or if your network helpfully firewalls outbound DNS requests, you’ll likely want to use your own resolver instead.
The upstream docker FAQ just tells people to disable the local resolver and/or hardcode some DNS entries in the docker config. That’s not very helpful when you roam and you really want to use your local resolver… To do things properly, you’ll have to set two bits of configuration: tell docker to use the local resolver, and tell your local resolver to listen on the docker interface.
Docker DNS configuration
First, you need to configure the docker daemon to use the host as DNS server: In the /etc/docker/daemon.json file, set the dns key to [“172.17.0.1”] (or whatever IP address your docker host is set to use).
Local resolver: dnsmasq in NetworkManager
When NetworkManager is set up to use dnsmasq, it runs with a configuration that’s built dynamically, and updated when the network settings change (e.g. to switch upstream resolvers according to DHCP or VPN settings).
You can add drop-in configurations in the /etc/NetworkManager/dnsmasq.d directory. I have set the following configuration variables in a /etc/NetworkManager/dnsmasq.d/docker.conf file:
# Makes dnsmasq bind to all interfaces (but still only accept queries on localhost as per NetworkManager configuration) bind-interfaces # Makes dnsmasq accept queries on 172.17.0.0/16 listen-address=172.17.0.1
Restarting NetworkManager brings up a new instance of dnsmasq, which will let Docker do its thing.
Local resolver: unbound
The same sort of configuration is done with unbound. We tell unbound to listen to all interfaces and to only accept recursion from localhost and the docker subnet. In /etc/unbound/unbound.conf.d/docker.conf:
interface: 0.0.0.0 interface: :: access-control: 127.0.0.0/8 allow access-control: ::1/128 allow access-control: 172.17.0.0/16 allow
Restart unbound (reload doesn’t reopen the sockets) and your docker container will have access to your local resolver.
Both these local resolver configurations should work even when the docker interface comes up after the resolver: we tell the resolver to listen to all interfaces, then only let it answer to clients on the relevant networks.
$ docker run busybox nslookup pypi.python.org Server: 184.108.40.206 Address 1: 220.127.116.11 google-public-dns-a.google.com Name: pypi.python.org Address 1: 2a04:4e42::223 Address 2: 2a04:4e42:200::223 Address 3: 2a04:4e42:400::223 Address 4: 2a04:4e42:600::223 Address 5: 18.104.22.168 Address 6: 22.214.171.124 Address 7: 126.96.36.199 Address 8: 188.8.131.52
$ docker run busybox nslookup pypi.python.org Server: 172.17.0.1 Address 1: 172.17.0.1 Name: pypi.python.org Address 1: 2a04:4e42:1d::223 Address 2: 184.108.40.206