BIND DNS Container on Mikrotik RouterOS
For a long time, I’ve run my own DNS (typically just BIND), originally on re-purposed thing clients, then on Raspberry Pis. A year or two ago, being very busy and working long hours, I decided I didn’t have much time to maintain my home network and started to simplify it. After reading about RouterOS’s capabilities for DNS forwarding with static host entries, I gave it a try. It performed well, and while I would lose capabilities, it did everything I needed it to at the time. I copied all the static entries from my zone file to my primary router, then archived my DNS server configs and shut them down. It’s worked well for a while, but I am now beefing up my home network/lab and decided I wanted full DNS capabilities back.
Since I last ran BIND on a Raspberry Pi, I have upgraded my home gateway to a Mikrotik RB5009UG+S+IN. This is a very capable little machine, with a GB of RAM, a quad-CPU ARM processor, and a GB of flash. It is also capable of running containers natively! Docker and the whole container concept emerged after I shifted roles in my career, going from smaller organizations where I had broader technical responsibilities, to larger organizations where I was much more focused on narrower network/security domains (and more time managing organizational politics). Consequently, I have never taken the time to get up to speed on something that’s common to most newer IT pros. This was a perfect opportunity to fix that!
After researching the basics, here is what I learned in my crash course on containers in general, and setting one up on a Mikrotik device.
Basic requirements:
- A Mikrotik device with sufficient CPU and RAM
- An external USB-connected storage device
- A container image with BIND9 that supports your hardware
- Choose a subnet for the container
Mikrotik hardware
This was not as easy to calculate scientifically. I know that BIND doesn’t require much in terms of CPU or RAM, but how do you figure out requirements for a container? I could never load my Raspberries enough to see any issues, so after looking at average CPU and memory utilization on the RB5009, I made an educated guess that it would be a non-issue.
Storage device
I originally tested a low-profile SanDisk thumb drive, and while I’m sure that would have worked for a test, they are not designed to be used continuously as a disk drive and knew that wasn’t a good idea if I intended to use it “for real.” So, I sacrificed my NVMe to USB-C SSD enclosure for the cause, popped in a 250GB stick I had lying around, and plugged it in to the router. (Note: I have no idea exactly how RouterOS manages “disks,” but having plugged in the thumb drive first, the NVMe seems to be permanently labeled as “usb2″—operationally immaterial but drives my brain crazy!)
BIND9 Container
Being completely inexperienced with the whole Docker/container world, I spent a couple hours reading up on it and trying to figure out how to find and download a container. (Yes, yes, eventually I’ll figure out how to build my own, but I’m a newbie right now.) After a few missteps, including thinking I needed to download an image to my workstation first, and trying the official ISC container image without success (CPU architecture mismatch, despite what it said on the Docker Hub description), I came across a pre-built BIND9 container for Mikrotik, see https://hub.docker.com/r/tecspace/bind9-mikrotik. In this process I learned about the Docker Hub Container Image Library, and discovered that RouterOS can download an image directly, given the name from the Hub.
Process
I mostly used WinBox, though I did sometimes resort to the terminal—remember, I’m new at this, and as much experience as I have with Mikrotik, I don’t RouterOS daily, so yes, I used my crutch. (I did also discover that you need WinBox 4.x, as 3.x has bugs. However, that actually led me to a pleasant surprise—WinBox 4.x has a native Linux binary, no more WINE needed. Not that WINE was a problem, but this is a nicer experience. The little things that make me happy.)
In any case, here’s what I went through to get this up and running (in WinBox, and YMMV):
- Install container package
- Download the Mikrotik container package from https://mikrotik.com/download (match your CPU architecture and RouterOS version)
- Upload it to Files by dragging it onto WinBox
- Reboot the router
- Enable containers and reboot
- Enter this at the terminal: /system/device-mode/update container=yes
- Cold boot (power cycle) the router
- Format Storage Disk
- Plug storage disk into USB
- Go to System > Disks
- If you don’t see usb1 (or maybe usb2, like for me) in the list, the system didn’t recognize it (I had this happen with a cheap NVMe to USB enclosure I got off Amazon—junked that and used the Best Buy enclosure and had no further issues)
- Click Format Drive, select ext4 and let ‘er rip
- Create file system structure
- Go to Files
- Once you’ve attached and formatted the disk, you will see a folder representing it here (usb1)
- Files and directories appear to be stored as a Key > Value pair, with the Key representing the full path with slashes.
- Click New > Directory and create the following
- File Name = bind9, Directory = usb1 (base directory to hold everything related to BIND)
- File Name = cache, Directory = usb1/bind9 (/var/cache/bind)
- File Name = etc, Directory = usb1/bind9 (/etc/bind)
- File Name = hints, Directory = usb1/bind9 (/usr/share/dns – discovered I needed this when I copied my config from my Raspberry DNS server)
- File Name = lib, Directory = usb1/bind9 (/var/lib/bind)
- File Name = log, Directory = usb1/bind9 (/var/log)
- Upload config from existing BIND setup
- I was able to upload all of my existing config files to the appropriate locations in Files and get things to work with no changes from my original Debian 12 BIND9 setup, so long as I created all locations and uploaded all referenced files.
- Go to Files
- Create container mounts
- Go to Container > Mounts > New and create the following:
- List = MOUNT_BIND9, Src = /usb1/bind9/cache, Dst = /var/cache/bind
- List = MOUNT_BIND9, Src = /usb1/bind9/etc, Dst = /etc/bind
- List = MOUNT_BIND9, Src = /usb1/bind9/hints, Dst = /usr/share/dns
- List = MOUNT_BIND9, Src = /usb1/bind9/lib, Dst = /var/lib/bind
- List = MOUNT_BIND9, Src = /usb1/bind9/log, Dst = /var/log
- Go to Container > Mounts > New and create the following:
- Create environment variables
- Note: I created these all as “disabled,” as I was able to accept the defaults and let BIND pull everything else from the config files. See notes on Docker Hub for the container, as well as BIND documentation.
- Go to Container > Envs > New and create the following:
- List = ENV_BIND9, Key = BIND9_STAY_ON, Value = 1
- List = ENV_BIND9, Key = BIND9_CMD_OPTS, Value = -g -4 -u named
- List = ENV_BIND9, Key = BIND9_CFG_OPTS, Value = version “hidden”;directory “/var/cache/bind”;listen-on {any;};listen-on-v6 {none;};recursion yes;allow-query {0.0.0.0/0;};d/system/device-mode/update container=yesnssec-validation yes;max-cache-size 64M;
- I relied heavily on a Mikrotik tutorial for all steps going forward, but posting them here in case the link goes stale
- Network setup
- Create new bridge for container networking:
- /interface/bridge/add name=bridge_containers
- Assign an IP address to the bridge:
- /ip/address/add address=172.16.0.1/24 interface=bridge_containers
- Create a new virtual interface:
- /interface/veth/add name=veth1 address=172.16.0.2/24 gateway=172.16.0.1
- Assign interface to bridge:
- /interface/bridge/port add bridge=bridge_containers interface=veth1
- Create a NAT for outgoing traffic:
- /ip/firewall/nat/add chain=srcnat action=masquerade src-address=172.16.0.0/24
- Create new bridge for container networking:
- Configure registry info (I am not positive I had to do this)
- /container/config/set registry-url=https://registry-1.docker.io tmpdir=usb1/tmp
- Create the container
- /container/add remote-image=
tecspace/bind9-mikrotik:1.0interface=veth1 root-dir=usb1/images/bind9 mountlists=MOUNT_BIND9 envlist=ENV_BIND9 start-on-boot=yes name=bind9
- /container/add remote-image=
- Check the status of the container and wait until downloading/extracting has been finished and the status=stopped:
- /container/print
- Start the Containter:
- /container/start bind9
All done
If you’ve done everything right, you should now have a functioning DNS server, running in a container on your router. From what I’ve observed, it uses very little CPU and RAM, causing essentially no impact to the router. Forwarded queries are resolved a hair faster (milliseconds) than the router’s own DNS forwarding. I consider this a great success!
A note about testing DNS performance
Quick shout out to my favorite DNS benchmarking tool: https://www.grc.com/dns/benchmark.htm
Posted: March 9th, 2026 under Draft.
Comments: none