Local Development with Wildcard DNS

The holy-grail of local development is wildcard DNS: the ability to have *.local.company.com pointing to localhost, your development machine. It doesn’t matter if you’re working on website.local.company.com or api.local.company.com, there’s no additional configuration necessary as you start working on new projects.

Unfortunately macOS doesn’t support wildcard entries in the /etc/hosts file – no OS does out of the box.

Dnsmasq

Dnsmasq is a tiny and incredibly popular DNS server that you can run locally, and supports wildcard domain resolution with very little configuration.

brew install dnsmasq

Now lets setup the configuration directory and configure dnsmasq to resolve all of our development domains.

You’ll want to avoid the *.dev and *.local domains for development. .dev exists as a real TLD in the ICANN root. .local is used by the Bonjour service on macOS. I recommend using *.local.companyname.com or *.lan

mkdir -pv $(brew --prefix)/etc/

cat >$(brew --prefix)/etc/dnsmasq.conf <<EOL

# Add domains which you want to force to an IP address here.
# The example below send any host in *.local.company.com and *.lan to a local
# webserver.
address=/local.company.com/127.0.0.1
address=/lan/127.0.0.1

# Don't read /etc/resolv.conf or any other configuration files.
no-resolv
# Never forward plain names (without a dot or domain part)
domain-needed
# Never forward addresses in the non-routed address spaces.
bogus-priv

EOL

Then lets configure launchd start dnsmasq now and restart at startup:

sudo brew services start dnsmasq

Finally lets validate that our dnsmasq server is configured to respond to all subdomains of local.company.com by running:

$ dig nested.test.local.company.com @127.0.0.1

; <<>> DiG 9.8.3-P1 <<>> nested.test.local.company.com @127.0.0.1
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 64864
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;nested.test.local.company.com.	IN	A

;; ANSWER SECTION:
nested.test.local.company.com. 0 IN	A	127.0.0.1

;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Sat Apr  8 11:15:17 2017
;; MSG SIZE  rcvd: 63

Integration using /etc/resolver

At this point we have a working DNS server, but it’s meaningless because macOS won’t use it for resolving any domains.

We can change this by adding configuration files in the /etc/resolver directory.

sudo bash -c 'echo "nameserver 127.0.0.1" > /etc/resolver/local.company.com'
sudo bash -c 'echo "nameserver 127.0.0.1" > /etc/resolver/lan'

Each domain that we configured in dnsmasq should have a corresponding entry in /etc/resolver/

Next, lets test that our resolver entries have been picked up by macOS.

$ scutil --dns

...
resolver #8
  domain   : local.company.com
  nameserver[0] : 127.0.0.1
  flags    : Request A records
Reachable, Directly Reachable Address
...

Fin

Testing you new configuration is easy; just use ping check that you can now resolve your local subdomains:

# Make sure you haven't broken your DNS.
ping -c 1 www.google.com

# Check that .local.company.com & .lan names work
ping -c 1 this.is.a.test.local.company.com
ping -c 1 this.domain.does.not.exist.lan

This is useful in particular for developers of microservices: your orchestration platform can dynamically generate hostnames, and you won’t have to worry about your /etc/hosts file again.

References

  • http://asciithoughts.com/posts/2014/02/23/setting-up-a-wildcard-dns-domain-on-mac-os-x/
  • https://gist.github.com/eloypnd/5efc3b590e7c738630fdcf0c10b68072
  • https://passingcuriosity.com/2013/dnsmasq-dev-osx/
  • http://serverfault.com/questions/118378/in-my-etc-hosts-file-on-linux-osx-how-do-i-do-a-wildcard-subdomain
  • https://gist.github.com/ogrrd/5831371

Jason Kulatunga

Build Automation & Infrastructure guy @Adobe. I write about, and play with, all sorts of new tech. All opinions are my own.

San Francisco, CA blog.thesparktree.com

Subscribe to Sparktree

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!