Start running your own DNS

As Web developers, one of the first things we do when starting a new project, is creating a development environment as similar as possible to the final destination of our code, AKA Production.
One of the most common requirements is running your apps in their own private domain and one of the most common solution is editing the /etc/hosts file, every time you need to add a new domain.
It’s no secret that this process can quickly become very tedious.
If you work on OS X, you probably have heard of Pow, but if you only need the domain resolution or don’t work with Ruby, it is probaly overkill to install a full featured application server just to create some dev domain.
MAMP Pro offers domain resolution too, but it’s not free.

Luckily, there is another solution: running your own instance of a DNS server.
As over-the-top as it may sound, DNS is generally a very lightweight service, you’ll be making just a few thousands queries a day, not a big deal at all, and it can have a beneficial effect on latency, speeding up requests by caching them locally, without hitting the network.
What you need it’s a copy of dnsmasq and stop worrying and love the shell.

First of all install dnsmasq and put it in autostart

brew install dnsmasq  
sudo cp -v $(brew --prefix dnsmasq)/*.plist /Library/LaunchDaemons

Configure the dns intance

cat <<- EOF > $(brew --prefix)/etc/dnsmasq.conf
# IPV4
address=/dev/127.0.0.1
# IPV6 or virtual hosts in Maverick won't work
address=/dev/::1
listen-address=127.0.0.1
EOF

Then configure the resolvers for all domains and create the brand new one for the .dev suffixes

# it might already exists
sudo mkdir -p /etc/resolver

# .dev domains
sudo bash -c 'echo "nameserver 127.0.0.1" > /etc/resolver/dev'

# universal catcher
sudo bash -c 'echo "nameserver 127.0.0.1" > /etc/resolver/catchall'
sudo bash -c 'echo "domain ." >> /etc/resolver/catchall'

You need to set localhost as the main DNS server, unfortunately you can’t automatically prepend localhost to the list of DNS your DHCP assigned to you.
You have to change it manually and put it on top.

networksetup -setdnsservers Ethernet 127.0.0.1
networksetup -setdnsservers Wi-Fi 127.0.0.1

If everything’s ok, running scutil --dns should return something like this

resolver #8
domain   : dev
nameserver[0] : 127.0.0.1
flags    : Request A records, Request AAAA records
reach    : Reachable,Local Address      

Now you can start dnsmasq

# start dnsmasq
sudo launchctl -w load /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist

# make some tests
$ ping -c 1 anyhostname.dev
PING anyhostname.dev (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.058 ms 

$ dig anyhostname.dev
;; ANSWER SECTION:
anyhostname.dev.    0   IN  A   127.0.0.1
;; Query time: 3 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)


$ dig google-public-dns-a.google.com @127.0.0.1
;; ANSWER SECTION:
google-public-dns-a.google.com. 25451 IN A  8.8.8.8
;; Query time: 30 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)  

# cache will speedup subsequent DNS queries
$ dig google-public-dns-a.google.com @127.0.0.1
;; Query time: 0 msec

Bonus: if you run Apache on your dev machine, you can easily setup the one virtual host to rule them all through mass virtual hosting.

Edit /etc/apache2/extra/httpd-vhosts.conf (your virtual host configuration could be somewhere else) and add these lines below.

Every .dev domain will point to a folder inside ~/Sites, with the same name, but without the extension.
For example, myapp.dev will point to ~/Sites/myapp.
You can of course customize your own environment: you could, for example, create a folder to hold all the virtual hosts created with this tecnique, just like Pow does, or if you are a Pow user already, reuse ~/.pow folder as a container.

<VirtualHost *:80>
    ServerAdmin you@localhost
    ServerName anyhostname.dev
    ServerAlias *.dev

    UseCanonicalName Off
    # %1.0 is the domain without the extension, in this case
    # everything before .dev
    # more info: http://httpd.apache.org/docs/2.4/mod/mod_vhost_alias.html
    VirtualDocumentRoot /Users/<your_login>/Sites/%1.0
    ErrorLog "/var/log/apache2/dev_hosts_error_log"
    CustomLog "/var/log/apache2/dev_hosts_access_log" common

    <Directory /Users/<your_login>/Sites/*>
        AllowOverride All
    </Directory>    
</VirtualHost>

You can finally reload Apache configuration with sudo apachectl graceful.

Leave a Reply

wpDiscuz