I think all of us IT folks have at one point or another poisoned our own DNS to test a web application before rolling it out to production. “A records” are easy to deal with because most modern operating systems still have something called a hosts file. But what about CNAMEs? Amazon’s Application Load Balancer for example does not provide an IP based endpoint but rather will give you a URL like “example-gw-41709394-ap-northeast-2.elb.amazonaws.com”. You will then have to create a CNAME on your domain’s DNS configurations to point your domain to it to make things work.

Now, what I am about to propose is probably not the only way to go about this but it certainly is a fairly convenient solution thanks to Docker and the wonderful Docker community out there. I also feel like this is the best way I can think of so-far because changing the URL of CMS and E-Commerce sites (Editing the code and database entries) to a test URL is not desirable as I want to minimize the steps of the production roll-out to make it feel like I am literally just flipping a switch. That, and I always like to replicate the production environment as close to the live configuration as possible so that I am not caught scrambling to fix something not covered by our testing during rollout.

But, “why containerize?” you may ask. Well, here’s a quick list on why:

Containers are extremely portable and I can take this container and throw it on any workstation or server quicker than any other way I know I can start and stop a customized set of services or components on-demand with a single command Containers are a great way to spin up disposable services without tainting your host system with libraries and config files that you won’t need afterwards. Just clean up by removing the container. Done! If I choose to open up BIND to other teammates, my server or workstation is a little more protected as the services opened to the outside world is sandboxed into its own space that’s segregated from the rest of my host.

With the above question out of the way, let’s talk a little bit about what my solution is. Essentially, it consists of the following 2 components:

A container running the sameersbn/docker-bind image (BIND with Webmin pre-installed) on my Ubuntu 18.04 host (My Laptop)*

A couple of bash scripts that will run via aliases to automate both starting/stopping of this container and the associated /etc/resolv.conf edits required to make it work

The idea is for me to be able to start/stop BIND and change in and out of using the container as the DNS server for my queries with just 2 simple command aliases. This way, I can switch between maintaining and troubleshooting the production environment and testing the new replacement infrastructure on one single machine effortlessly.

So without further ado, here’s what I did to put together the solution:

Let’s start with installing Docker if it’s not already installed:

sudo apt install docker.io

If we don’t want to keep typing sudo to run docker commands, add our user to /etc/group. In my case, it’s “dragon”:

sudo vim /etc/group

The docker line in /etc/group should look something like this:

docker:x:127:dragon

Then, save, exit, and reboot as a lazy way to make sure the permissions take effect even though I am sure there are ways to do it without a reboot.

After the laptop reboots, let’s pull the sameersbn/docker-bind image:

docker pull sameersbn/bind:9.11.3-20180713

9.11.3-20180713: Pulling from sameersbn/bind

6b98dfc16071: Pull complete

4001a1209541: Pull complete

6319fc68c576: Pull complete

b24603670dc3: Pull complete

97f170c87c6f: Pull complete

701c34001733: Pull complete

61f415ec66af: Pull complete

92a47682d82d: Pull complete

7428c3119443: Pull complete

Digest: sha256:53188ff6ffeca017b4451203eea419373e52b42df3434614fb6d7e582475a4b4

Status: Downloaded newer image for sameersbn/bind:9.11.3-20180713

Then, create a directory for the container volume:

sudo mkdir -p /srv/docker/bind

Now, we are ready to give the container a go:

docker run --name bind -d --volume /srv/docker/bind:/data sameersbn/bind:9.11.3-20180713

24bf3c7fa926d07f9adcf5512df9e974e7cce0950f05f01a0f15bfd3cf1dbe7b

The above random string in the output tells me that it launched successfully but let’s check anyways:

docker ps

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

24bf3c7fa926 sameersbn/bind:9.11.3-20180713 "/sbin/entrypoint.sh…" 10 seconds ago Up 3 seconds 53/tcp, 10000/tcp, 53/udp bind

Great! Looks like it’s really up! Believe it or not, you already have a fully functional DNS server with Webmin preinstalled running and ready to go. Super fast right? Let’s find out what the IP address of this container is so we can connect to it:

docker inspect bind |grep IPAddress

"SecondaryIPAddresses": null,

"IPAddress": "172.17.0.2",

"IPAddress": "172.17.0.2",

Now that we know what the IP is, let’s take a look with a web browser (https://172.17.0.2:10000) and by-pass any SSL warnings as the cert will be self-signed:

Bam! I wasn’t lying was I? We got a login page! The default username and password is “root” and “password” but I would suggest that you change the root password on the container. To do so, run the following command:

docker exec -ti bind passwd

Enter new UNIX password:

Retype new UNIX password:

passwd: password updated successfully

Now that we have the password changed, let’s login to the Webmin console, click on “Servers” on the menu on the left of the site, and click on “BIND DNS Server”. This will bring you to the below screen:

From here, click on the last large button called “Edit Config File”.

Then, click on the drop down menu above the config file editing area on the next screen and select /etc/bind/named.conf.options.

Find this line in the config file editing area:

listen-on-v6 { any; };

Comment out that line and add a few more lines directly below it like so:

# listen-on-v6 { any; };

listen-on port 53 { any; };

forwarders {

8.8.8.8;

8.8.4.4;

};

Hit “Save and Close” below.

This will bring you back to the main screen for BIND config. Note that I forward all queries when no local records are available to the Google DNS servers in the above config block.

From there, scroll down to the following part of the page and click on the sneaky little button that’s highlighted in red to create a master zone for your domain:

Enter the relevant information like below:

As you can tell, I created a master zone for dragon-ventures.com which is a domain that’s already used in production (global DNS). You can even see that there’s a website associated with the domain in global DNS servers:

Next up, we create a CNAME record for www.dragon-ventures.com. From the BIND main page click on the button highlighted in red:

Then, select the following to create a CNAME record for www.dragon-ventures.com:

Fill things out as follows and click on the green “Create” button:

Notice that I am pointing www.dragon-ventures.com to the 3DF opensource initiative site. Note: don’t forget to add a “.” after the Real Name or you will be wondering for hours why the DNS server is not resolving your CNAME. So it should be:

osi.3df.io.

and NOT:

osi.3df.io

Let’s then return to the BIND main page and click on the apply changes button:

And that’s it for setting up the DNS part. Now let’s get the laptop to use this DNS server. For now, let’s stop the container as we will be starting it with a script later:

docker stop bind

First make a directory for where all your personal customized scripts will be. For me, it’s ~/.scripts

mkdir ~/.scripts

Then, create a bash script called usebind.sh in ~/.scripts/ to start the container and edit /etc/resolv.conf to use the container as its DNS server:

vim ~/.scripts/usebind.sh

Copy and paste the below, save, and exit:

#!/bin/bash # Start bind container

docker start bind # Comment out any line that starts with "nameserver"

sudo sed -e '/nameserver/s/^/# /g' -i /etc/resolv.conf # Add "nameserver 172.17.0.0.2" at the end of the file

echo 'nameserver 172.17.0.2' | sudo tee --append /etc/resolv.conf > /dev/null

Then, create a bash script called unbind.sh in ~/.scripts/ to revert the changes in /etc/resolv.conf back to use your default DNS server and then stop the container:

vim ~/.scripts/unbind.sh

Copy and paste the following, save, and exit:

#!/bin/bash # Remove any "#" from any line that consist of the word "nameserver"

sudo sed -i '/^#.* nameserver /s/^# //' /etc/resolv.conf # Remove any line that consists of "172.17.0.2"

sudo sed -i '/172.17.0.2/,+1 d' /etc/resolv.conf # Stop bind container

docker stop bind

Make sure it’s executable by us:

chmod u+x ~/.scripts/*bind*

Then, because I want to be able to type a short and simple command from anywhere in the terminal, I create two new aliases by either editing the existing ~/.bash_aliases or creating it if there’s none:

vim ~/.bash_aliases

Copy and paste the following, save, and exit:

# Dockerized DNS Server Start/Stop

alias usebind='~/.scripts/usebind.sh'

alias unbind='~/.scripts/unbind.sh'

Now let’s source the aliases so we don’t have to logout and then log back in for this to work:

source ~/.bashrc

That’s it. Technically, we are all set so let’s test it!

Run the usebind.sh script:

usebind

bind

[sudo] password for dragon:

It should be practically instant. Since it’s the first time running this command, let’s check if the script worked properly:

docker ps

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

24bf3c7fa926 sameersbn/bind:9.11.3-20180713 "/sbin/entrypoint.sh…" 15 minutes ago Up 11 seconds 53/tcp, 10000/tcp, 53/udp bind

cat /etc/resolv.conf |grep nameserver

# nameserver 127.0.0.53

nameserver 172.17.0.2

Alright, it’s exactly what we expect it to do. The container is running and our /etc/resolv.conf looks good. So let’s use ping to find out if I am really using the DNS container to resolve my queries:

ping osi.3df.io

PING osi.3df.io (104.31.83.214) 56(84) bytes of data.

ping www.dragon-ventures.com

PING osi.3df.io (104.31.83.214) 56(84) bytes of data. PING osi.3df.io (104.31.83.214) 56(84) bytes of data.PING osi.3df.io (104.31.83.214) 56(84) bytes of data.

Looks like www.dragon-ventures.com is in fact resolving to the same IP address as osi.3df.io so I definitely call this a pass.

Then, let’s check the final piece which is whether the unbind alias works as expected:

unbind

bind

[sudo] password for dragon:

Check /etc/resolv.conf:

cat /etc/resolv.conf |grep nameserver

nameserver 127.0.0.53

Check if container is running:

docker ps

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

Check with ping:

ping www.dragon-ventures.com

PING PING www.dragon-ventures.com (104.31.91.41) 56(84) bytes of data.

DNS settings look good, container is no longer running, and ping returns a different IP address for www.dragon-ventures.com. This proves that our unbind.sh script also works.

From this point on whenever you want to test any of your replacement infrastructure that has a CNAME endpoint, all we have to do is open up a terminal and do this:

usebind

bind

[sudo] password for dragon:

Then start testing away!

When we are all done and want to go back to maintaining or troubleshooting the production environment, all we have to do is:

unbind[sudo] password for dragon:

bind

Hope this post will help someone simplify their CNAME testing related problems out there!

*This solution should also work for Mac and Windows given a little bit of tweaking.