Skip to main content

Running a DNS Server with Docker

Shenzhen, China

This tutorial is largely based on the Medium article by foo0x29a.

Besides the standard numbers-and-dots notation for Internet addresses, you can also refer to a host by a symbolic name. The advantage of a symbolic name is that it is usually easier to remember. For example, the machine with Internet address is also known as; and other machines in the domain can refer to it simply as alpha. The Domain Name System (DNS) is a service that translates domain names into IP addresses.

Internally, the system uses a database to keep track of the mapping between host names and host numbers. The name resolution in the Linux environments is described in the /etc/nsswitch.conf file. By default, it has an entry with files dns, which means it will first check the /etc/hosts file, and then the DNS server.

BIND 9 is transparent open source, licensed under the MPL 2.0 license. BIND 9 has evolved to be a very flexible, full-featured DNS system. BIND is used successfully for every application from publishing the (DNSSEC-signed) DNS root zone and many top-level domains, to hosting providers who publish very large zone files with many small zones, to enterprises with both internal (private) and external zones, to service providers with large resolver farms.


Network Setup

To be able to operate the DNS container with static IP addresses I am going to create Docker network first. The following command creates an arbitrary called network instar-net with range

docker network create --subnet= instar-net

DNS Server Configuration

First I create a file to start configuring the Bind9 server named.conf.options:

mkdir -p /opt/bind9/configuration
nano /opt/bind9/configuration/named.conf.options

This will make sure that BIND is listening on all interfaces and will use the Google DNS Servers as forwarders:

options {
directory "/var/cache/bind";

recursion yes;
listen-on { any; };

forwarders {;;

Next, I will Define a Zone called, that points to /etc/bind/zones/ Zone File:

nano /opt/bind9/configuration/named.conf.local
zone "" {
type master;
file "/etc/bind/zones/";

The Zone File called lists all the services that need to be managed (e.g. Docker container on the Docker network) and assigns them a hostname and an IP address:

nano /opt/bind9/configuration/
$TTL    1d ; default expiration time (in seconds) of all RRs without their own TTL value
@ IN SOA (
3 ; Serial
1d ; Refresh
1h ; Retry
1w ; Expire
1h ) ; Negative Cache TTL

; name servers - NS records

; name servers - A records IN A IN A IN A

In the example, there are two hosts and, and one name server

Build the Docker Image

I want to use the official Docker Image of BIND 9 but install a couple of additional dependencies and directly add my configuration files to it instead of mounting them into the container:

nano /opt/bind9/Dockerfile
FROM internetsystemsconsortium/bind9:9.18

RUN apt update \
&& apt install -y \
bind9-doc \
dnsutils \
geoip-bin \
mariadb-server \

# Copy configuration files
COPY configuration/named.conf.options /etc/bind/
COPY configuration/named.conf.local /etc/bind/
COPY configuration/ /etc/bind/zones/

# Expose Ports
EXPOSE 53/tcp
EXPOSE 53/udp
EXPOSE 953/tcp

# Start the Name Service
CMD ["/usr/sbin/named", "-g", "-c", "/etc/bind/named.conf", "-u", "bind"]

I can now build and tag the BIND image:

docker build -t ddns-master .

Run the Docker Container

The container has now to be created inside the Docker network instar-net with the IP address assigned to it inside

docker run -d --rm --name=ddns-master --net=instar-net --ip= ddns-master

I can now verify my server configuration:

docker exec -ti ddns-master /bin/bash
named-checkzone /etc/bind/zones/
zone loaded serial 3

Connecting Services

Now it is possible to run the two service container using the dns-server container as a DNS server (I am using NGINX container because I already have the image. Use whatever container you want):

docker run -d --rm --name=service1 --net=instar-net --ip= --dns= nginx:1.21.6-alpine /bin/ash -c "while :; do sleep 10; done"
sudo docker run -d --rm --name=service2 --net=instar-net --ip= --dns= nginx:1.21.6-alpine /bin/ash -c "while :; do sleep 10; done"

All container now run on the same network:

docker network inspect instar-net

"Name": "instar-net",
"IPAM": {
"Config": [
"Subnet": ""
"Containers": {
"04bd7e3b3a033fd643d36fff787cda485dc5f3d4468212568b8ff4498e776993": {
"Name": "ddns-master",
"IPv4Address": ""
"14deb32e260d15ff8543571f2c5fd1d99eeb9ba97042a97c34d9b933525ca8aa": {
"Name": "service2",
"IPv4Address": ""
"cb6840cfd76d360dfe4cefc96486a11cd4b73f405d114c2830fd792c4883dd8b": {
"Name": "service1",
"IPv4Address": ""

I can test the DNS Service by connecting to one of the client service and ping the other:

docker exec -it service1 nslookup                                                         


Also the forwarder is doing it's job allowing me to resolve domains outside of the defined zone:

docker exec -it service1 nslookup                                                                     

Non-authoritative answer:

Here we can see that the reply is Non-authoritative - meaning that the DNS server we asked had to forward the request because it did not know the answer.