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:
|
|
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.
|
|
Bring the container up:
|
|
The container will create the required directory structure. Once it’s done, stop it:
|
|
Next, we’ll copy the named.conf file over the default:
|
|
Update the configuration to match your domain and network:
- Replace “example.com” with your domain
- Update zone file names to match
- 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:
|
|
These filenames must match what you defined in named.conf.
- Update the forward zone file, replacing “example.com” with your chosen domain and modifying the records to match your environment
- Update the reverse zone file to match your environment
- Bring the container back up
|
|
Confirm that resolution for your domain is working by using nslookup:
|
|
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:
|
|
Open named.conf and update the trusted_transfer ACL to include both DNS server IPs:
|
|
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:
|
|
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.
|
|
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.
|
|
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:
|
|
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.
|
|
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.
Access Your Homelab from Anywhere.
You now have a working DNS setup that you control and understand.
The next step is putting it to use beyond your internal network without introducing unnecessary exposure. That’s where Twingate fits in naturally - take a look at how that works without opening ports.
DNS in the Homelab
Designing Your Homelab DNS
Understanding DNS Records