Foundry81 > Homelab
Building a DNS server for the Homelab

Homelab DNS: Running BIND with Docker

Homelab DNS: Running BIND with Docker

Getting BIND running in Docker takes minutes. Running it correctly takes a bit more.

BIND is widely used and does one thing well; it provides domain name resolution services. It’s straightforward, easy to manage, and how I run BIND on my home network. By the time you’re done walking through the steps in this post, you’ll have multiple BIND servers answering for local domains and providing DNS resolution services on your network.

We’re going to be using the contents of my BIND Docker repo on GitHub. It contains the Dockerfile used to generate the container image, a ready-to-use Compose file, and a set of configuration templates. We’ll only need a handful of files from the repo, so we’ll grab them with Curl as needed.

What You’ll Need

BIND containers are lightweight in a Homelab; you’re serving a small number of zones and resolving requests for your own network. We’re building two servers, one primary and one secondary. In order to follow along, you’ll need:

  • Docker running on both hosts
  • IP addresses for both servers
  • Configuration files (from the GitHub repo)

While it’s possible to run both on one host using different ports for testing. Don’t do this long-term - it defeats BINDs redundancy and poses a downtime risk for your Homelab.

Deploy the primary

Download the docker-compose.yml file from my repository, saving to a folder that makes sense for your setup:

1
2
3
mkdir -p docker/bind
cd docker/bind
curl -fsSL https://raw.githubusercontent.com/Foundry81/docker-bind-dns/refs/heads/main/docker-compose.yml -o docker-compose.yml

It’s a simple container:

  • The image points to https://hub.docker.com/r/100781/bind-dns
  • The container name is “bind”
  • Port 53 is being exposed for both TCP and UDP
  • Two bind mounts – one stores configuration files while the other exposes BIND’s cache

The container runs unless explicitly stopped.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
services:
  bind:
    image: 100781/bind-dns
    container_name: bind
    ports:
      - "53:53/tcp"
      - "53:53/udp"
    volumes:
      - ./config:/bind
      - ./bind-cache:/var/cache/bind
    restart: unless-stopped

Bring the container up:

1
sudo docker compose up -d

The container will create the required directory structure. Once it’s done, stop it:

1
sudo docker compose down

Next, we’ll copy the named.conf file over the default:

1
2
cd config/etc
curl -fsSL https://raw.githubusercontent.com/Foundry81/docker-bind-dns/refs/heads/main/templates/authoritative-master/named.conf -o named.conf

Update the configuration to match your domain and network:

  1. Replace “example.com” with your domain
  2. Update zone file names to match
  3. Modify reverse zone to match your subnet (reversed)

If your subnet is 192.168.100.0/24, the reverse zone becomes: 100.168.192.in-addr.arpa Next, download the forward and reverse lookup files and edit them to fit your environment – starting with their proper names:

1
2
3
mkdir zones && cd zones
curl -fsSL https://raw.githubusercontent.com/Foundry81/docker-bind-dns/refs/heads/main/templates/authoritative-master/zones/db.example.com -o db.yourdomain.com
curl -fsSL https://raw.githubusercontent.com/Foundry81/docker-bind-dns/refs/heads/main/templates/authoritative-master/zones/db.192.168.100 -o db.192.168.100

These filenames must match what you defined in named.conf.

  1. Update the forward zone file, replacing “example.com” with your chosen domain and modifying the records to match your environment
  2. Update the reverse zone file to match your environment
  3. Bring the container back up
1
2
sudo docker compose up -d
sudo docker logs bind

Confirm that resolution for your domain is working by using nslookup:

1
2
nslookup
server 127.0.0.1

Try resolving a known record and one that doesn’t exist. Type exit to close nslookup and your primary DNS server is ready!

It’s a good idea for the next step to be setting up a secondary DNS server; if you’re poking around or having a bit of FAFO fun, go ahead and skip to the resolver setup steps instead.

Add a Secondary Server

Any DNS setup should have at least two servers for the sake of redundancy. Having only one server would mean the Internet “disappears” when it isn’t working properly. Adding a second server brings needed redundancy; adding more improves availability.

Bringing a secondary BIND server online is simple - follow the primary server setup steps until downloading the named.conf file and use this one instead:

1
2
cd config/etc
curl -fsSL https://github.com/Foundry81/docker-bind-dns/blob/main/templates/authoritative-slave/named.conf -o named.conf

Open named.conf and update the trusted_transfer ACL to include both DNS server IPs:

1
2
3
4
acl trusted_transfer {
   192.168.122.10; // Primary DNS
   192.168.122.20; // Secondary DNS
 };

Note: If you’re running Docker in rootless mode, you’ll need to include the Docker-assigned IP addresses in your trusted_transfer ACL for replication between the primary and secondary DNS servers to work.

Next, edit the zone information blocks to match the contents of your primary DNS zone’s configuration.

Restart the container:

1
2
sudo docker compose up -d
sudo docker logs bind

Check your logs for successful AXFR events. If replication is failing, check connectivity between the servers (ping one from the other and vice versa,) and ensure the correct IPs are listed in both config files.

1
2
nslookup
server 127.0.0.1

Try resolving ns1.yourdomain.com and you should get the same response as if you were making the request to it instead. Next, try resolving reddit.com. That didn’t work, and that’s just because we haven’t gotten to the resolver section of this post yet.

Add a Third Server

Tertiary servers can be added to a network and will increase redundancy. If you have another Docker host that’s running 24/7 – it’s likely a good candidate to be running another BIND instance.

Simply follow the process of adding a secondary DNS server, but update the master’s trusted_transfer ACL to include all of the DNS servers and include its IP to “also-notify” for all hosted zones it’ll answer for – to ensure replication works.

Resolver Setup

Now that you have two, or more, DNS servers answering authoritatively for your domain, let’s give them some more work to do and set them up as resolvers. Make the same change to the named.conf files for both your primary and secondary servers:

Change recursion to “yes” and add allow-recursion.

1
    recursion yes;

Without recursion enabled, your server only answers for zones it owns. Now, it’ll use forwarders to provide answers for other domains.

Add a list of forwarders to use when answering requests:

1
2
3
4
forwarders {
	9.9.9.9;
	1.1.1.1;                                                                                                                                     
};

The example above uses Quad9 and Cloudflare’s DNS servers. Forwarders don’t need to exist outside of your network – self-hosted DNS filtering software like PiHole can be treated as forwarders, providing an additional layer of security and privacy protection.

Restart the container and use nslookup to test resolution of external domains to confirm the servers are now functioning as resolvers.

Configure Clients

Now that you have DNS servers authoritatively answering for your domain, and also configured to push requests upstream to public DNS providers – it’s time to put them to work!

Configure clients to use your DNS servers. If you’re running DHCP, update the scope to use the IPs of your new DNS servers, too. As long as one of your DNS servers are online and accessible by other devices on your network, you’re all set!

Limiting Access via ACLs

Now that DNS is working, we can limit access via ACLs:

Use a “trusted” ACL to limit access to devices in specific IP ranges – or explicitly by IP. This controls who can query your server, send recursive queries, access the local query cache, and perform other functions.

1
2
3
4
5
6
7
acl trusted {                                                                                                                                                                                                                              
  localhost;        # Includes the loopback addresses 127.0.0.1 and ::1                                                                                                                                                                      
  localnets;        # Includes addresses/subnets of the server running BIND                                                                                                                                                                    
  192.168.100.0/24; # Trusted LAN
  172.16.0.0/12;    # Internal Docker networks
  10.0.0.5;         # Specific trusted machine                                                                                                                                                                                           
};

If the devices on your network are set to use your DNS servers, creating firewall rules that disallow outbound on port 53 for any IPs other than your DNS servers helps ensure your DNS servers, and no others, are being used.

What You Built

Having a properly working, redundant, DNS setup running locally brings a lot of benefits – more importantly, you’ve built something that behaves like a real-world deployment - not just a lab experiment.

If you brought a secondary server, or more online, you’ve introduced redundancies that ensure you’re running a real-world setup in a way that brings benefits – and stability. Nice move.

From here, everything gets easier: cleaner service access, simpler changes, and the ability to layer in tools like DNS filtering and reverse proxies without fighting your own infrastructure. Reverse proxies like NGINX Proxy Manager, Let’s Encrypt become usable too, and that’s what we’re covering next.

Further Reading

Getting in Touch

Have a question? Want to talk tech? Curious about something you saw here?

Reach out. I’m always up for a good conversation, answering a thoughtful question, or geeking out over infrastructure, design, or the overlap between them. I’ll get back to you when I can.

Looking to build something? Launch something? Fix something?

If you see alignment between your work and mine, let’s explore it. I collaborate with IT organizations, creative teams, and builders who value thoughtful execution and clear outcomes. If it’s a good fit, we’ll make it happen.