Four Kitchens
Insights

Local development with Apache VHosts and Dnsmasq

3 Min. ReadDevelopment

There are plenty of tools for doing local development, however some of them place some inconvenient limits on what you can do. If you aren’t afraid to edit a few config files then you can have an incredibly flexible local development in just a few steps.

At the end of this tutorial you’ll be able to create a folder, following the simple ~/Sites/MYFOLDERNAME.dev pattern, and have it just work in the browser.

Setting up Apache

OSX ships with Apache pre-installed, but in it’s default configuration it’s not very useful. Additionally at some point in the near past, Apple removed the ability to toggle Apache’s state from System Preferences, we’ll be solving that as well.

These instructions assume that you’ve never made any changes to the Apache configuration. If you have, your mileage may vary.

Create a Sites directory in your home directory if you don’t already have one.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
[ ! -d ~/Sites ] && mkdir ~/Sites
[ ! -d ~/Sites ] && mkdir ~/Sites
[ ! -d ~/Sites ] && mkdir ~/Sites

Create a blank httpd-vhosts.conf file, we’ll come back to this in a bit.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
touch ~/Sites/httpd-vhosts.conf
touch ~/Sites/httpd-vhosts.conf
touch ~/Sites/httpd-vhosts.conf

Create a symbolic link for our previously created conf file.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
sudo ln -s ~/Sites/httpd-vhosts.conf /etc/apache2/other
sudo ln -s ~/Sites/httpd-vhosts.conf /etc/apache2/other
sudo ln -s ~/Sites/httpd-vhosts.conf /etc/apache2/other

Setup the logs directory, and set it’s permissions. These are probably a little too permissive, feel free to adjust as needed.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
mkdir ~/Sites/logs && chmod 0777 ~/Sites/logs
mkdir ~/Sites/logs && chmod 0777 ~/Sites/logs
mkdir ~/Sites/logs && chmod 0777 ~/Sites/logs

Add the following to the previously created httpd-vhosts.conf file. Make sure to replace USERNAME with your own user name.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
Listen 80
# Use name-based virtual hosting.
NameVirtualHost *:80
UseCanonicalName Off
LogFormat "%V %h %l %u %t "%r" %s %b "%{Referer}i" "%{User-Agent}i"" dynamic_vhosts
CustomLog /var/log/apache2/access_log dynamic_vhosts
<VirtualHost *:80>
ServerName localhost
VirtualDocumentRoot /Users/USERNAME/Sites/%0
<Directory "/Users/USERNAME/Sites">
Options Indexes FollowSymLinks MultiViews
AllowOverride All
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
Listen 80 # Use name-based virtual hosting. NameVirtualHost *:80 UseCanonicalName Off LogFormat "%V %h %l %u %t "%r" %s %b "%{Referer}i" "%{User-Agent}i"" dynamic_vhosts CustomLog /var/log/apache2/access_log dynamic_vhosts <VirtualHost *:80> ServerName localhost VirtualDocumentRoot /Users/USERNAME/Sites/%0 <Directory "/Users/USERNAME/Sites"> Options Indexes FollowSymLinks MultiViews AllowOverride All Order allow,deny Allow from all </Directory> </VirtualHost>
Listen 80

# Use name-based virtual hosting.
NameVirtualHost *:80
UseCanonicalName Off

LogFormat "%V %h %l %u %t "%r" %s %b "%{Referer}i" "%{User-Agent}i"" dynamic_vhosts
CustomLog /var/log/apache2/access_log dynamic_vhosts

<VirtualHost *:80>
  ServerName localhost
  VirtualDocumentRoot /Users/USERNAME/Sites/%0

  <Directory "/Users/USERNAME/Sites">
    Options Indexes FollowSymLinks MultiViews
    AllowOverride All
    Order allow,deny
    Allow from all
  </Directory>
</VirtualHost>

Obviously feel free to change around the log format to best fit your application. The key to this working is the %0 at the end of VirtualDocumentRoot, acting as a placeholder for the domains you’ll be using locally.

At this point you should restart Apache to the previous configuration changes will be applied.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
sudo apachectl stop && sudo apachectl start
sudo apachectl stop && sudo apachectl start
sudo apachectl stop && sudo apachectl start

Setting up Dnsmasq

You could potentially stop without executing any of the following steps and have dynamic vhosts. However, every time you created a new domain you’ll need to update your hosts file. That’s no fun. Installing Dnsmasq is relatively straightforward.

Install Dnsmasq.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
brew install dnsmasq
brew install dnsmasq
brew install dnsmasq

Copy the default configuration to its new home.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
cp /usr/local/opt/dnsmasq/dnsmasq.conf.example /usr/local/etc/dnsmasq.conf
cp /usr/local/opt/dnsmasq/dnsmasq.conf.example /usr/local/etc/dnsmasq.conf
cp /usr/local/opt/dnsmasq/dnsmasq.conf.example /usr/local/etc/dnsmasq.conf

We’re going to be using the .dev domain for all local domains. Add the configuration to the newly created dnsmasq.conf file. If you want the .dev domains to point to a different address (i.e. A Vagrant instance), other than your local, just change 127.0.0.1 to your preferred location.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
echo 'address=/.dev/127.0.0.1' > $(brew --prefix)/etc/dnsmasq.conf
echo 'address=/.dev/127.0.0.1' > $(brew --prefix)/etc/dnsmasq.conf
echo 'address=/.dev/127.0.0.1' > $(brew --prefix)/etc/dnsmasq.conf

Now we need a place to let Dnsmasq know about our new resolvers. Additionally we’ll add our first resolver, .dev. Again, don’t forget to change 127.0.0.1 to the address you specified in the previous step if you intend to use something different.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
sudo mkdir -v /etc/resolver && sudo bash -c 'echo "nameserver 127.0.0.1" > /etc/resolver/dev'
sudo mkdir -v /etc/resolver && sudo bash -c 'echo "nameserver 127.0.0.1" > /etc/resolver/dev'
sudo mkdir -v /etc/resolver && sudo bash -c 'echo "nameserver 127.0.0.1" > /etc/resolver/dev'

Dnsmasq needs to start, and we want it to start at startup.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
sudo cp -fv /usr/local/opt/dnsmasq/*.plist /Library/LaunchDaemons
sudo launchctl load /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist
sudo cp -fv /usr/local/opt/dnsmasq/*.plist /Library/LaunchDaemons sudo launchctl load /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist
sudo cp -fv /usr/local/opt/dnsmasq/*.plist /Library/LaunchDaemons
sudo launchctl load /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist

If you need to restart Dnsmasq run the following.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
sudo launchctl unload /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist && sudo launchctl load /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist
sudo launchctl unload /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist && sudo launchctl load /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist
sudo launchctl unload /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist && sudo launchctl load /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist

Great! You can check to see that resolver is working with scutil --dns. You should see an additional resolver at the bottom, your exact number might vary.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
resolver #8
domain : dev
nameserver[0] : 127.0.0.1
resolver #8 domain : dev nameserver[0] : 127.0.0.1
resolver #8
  domain   : dev
  nameserver[0] : 127.0.0.1

Additionally you can ping any domain ending in .dev and it should resolve to 127.0.0.1

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
~ : ping -c 1 test.dev
PING test.dev (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.042 ms
--- test.dev ping statistics ---
1 packets transmitted, 1 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.042/0.042/0.042/0.000 ms
~ : ping -c 1 test.dev PING test.dev (127.0.0.1): 56 data bytes 64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.042 ms --- test.dev ping statistics --- 1 packets transmitted, 1 packets received, 0.0% packet loss round-trip min/avg/max/stddev = 0.042/0.042/0.042/0.000 ms
~ : ping -c 1 test.dev
PING test.dev (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.042 ms

--- test.dev ping statistics ---
1 packets transmitted, 1 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.042/0.042/0.042/0.000 ms

Final setup

You can now create any series of folders ending in .dev in your sites directory. The contents of those folders will be served on the corresponding domain. If you have a folder called test.dev then your domain will also be test.dev

Enjoy your new automatic local development environment.