[Guide] Local web development setup on a Mac


(Matthew Fay) #1

A long time ago I stumbled on this article:

It’s an excellent guide to set up a robust and useful local development server for web devs, including configuring the .test TLD for local use. For a long time, this was much more convenient in Apache due to some VirtualDocumentRoot magic. I’d absolutely recommend reading through it if you’re interested.

As Caddy’s grown and acquired more features (especially {labelN} Placeholders), I wanted to see if this guide could be streamlined and made simpler again using Caddy. So here we go: the Caddy version.


The Perfect Web Development Environment for Your New Mac, with Caddy

Required: Xcode and Homebrew

If you’re writing software on a Mac, you probably have heard of these or have them installed. Start with the Xcode developer command line tools:

xcode-select --install

There will be a dialogue box and may involve a large download. Once that’s done, grab Homebrew, the missing macOS package manager:

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

This one will go a bit faster!

Install software

Now you’ve got Homebrew, grab the packages we want to use:

brew install caddy dnsmasq php

Configure DNS

Lets configure dnsmasq and the system resolver first, so you can visit *.test websites in your browser and automatically resolve them locally. Start by adding .test to your dnsmasq config:

echo 'address=/.test/127.0.0.1' > $(brew --prefix)/etc/dnsmasq.conf

Next, tell the macOS system resolver to use the local dnsmasq server for requests for the .test TLD:

sudo bash -c 'mkdir /etc/resolver && echo "nameserver 127.0.0.1" > /etc/resolver/test'

Fire up dnsmasq:

sudo brew services start dnsmasq

You’ll note that this must be done with sudo (unlike the other brew commands in this guide). This is due to the requirement to bind port 53, which unprivileged accounts can’t do; dnsmasq must therefore run as root.

It will also now start automatically at boot.

Configure PHP-FPM

PHP-FPM comes configured out of the box on homebrew to listen on 127.0.0.1:9000. So lets fire it up:

brew services start php

And we’re done here.

Configure Caddy

Unfortunately, Caddy’s brew formula doesn’t define a .plist file, which is needed to run it as a macOS service. Fortunately, the Caddy repo has one for us to use: https://github.com/mholt/caddy/blob/master/dist/init/mac-launchd/com.caddyserver.web.plist. Download it to your /Library/LaunchDaemons folder:

sudo curl -o /Library/LaunchDaemons/com.caddyserver.web.plist https://raw.githubusercontent.com/mholt/caddy/master/dist/init/mac-launchd/com.caddyserver.web.plist

This .plist makes some assumptions about where it’s going to store your Caddyfile, ACME assets, and temp folder, so we’ll have to make sure they’re all ready to go:

sudo mkdir -p /etc/caddy /etc/ssl/caddy /var/log/caddy /usr/local/bin /var/tmp
sudo touch /etc/caddy/Caddyfile
sudo chown _www:_www -R /etc/caddy /etc/ssl/caddy /var/log/caddy
sudo chmod 0750 /etc/ssl/caddy

We’ll also want to make a place for our dev sites to go. I like to use ~/projects/www. You should use your own directory of choice for the following command.

mkdir -p ~/projects/www

Now lets write the Caddyfile:

sudo nano /etc/caddy/Caddyfile

Add the following configuration, replacing the root with the directory you chose a moment ago:

http://*.test {
  # Don't forget to update this site root!
  root /Users/whitestrake/projects/www
  fastcgi / localhost:9000 php
  rewrite {
    to /{label1}{uri} /home{uri} {uri}
  }
  log stdout
  errors stderr
}

And now lets run Caddy (and have it start up at boot):

sudo launchctl load -w /Library/LaunchDaemons/com.caddyserver.web.plist

And we’re done here, too. You can find logs for troubleshooting purposes in /var/log/caddy.

The end result

Put files in ~/projects/www/client1. Browse to http://client1.test. Enjoy the simplicity.

Add a ~/projects/www/home directory to use as a home or default page; then, if you accidentally browse to http://clientq.test (or another non-existent directory) instead, you’ll get that home page. Bonus points if you put a landing page here that links to your other projects.

If it can’t find a client1 folder, and there’s no home folder, it will try to serve a file directly inside ~/projects/www. You could put a simple index file here, too, if you like.

Optional: Adding a database

This should be as simple as:

brew install mysql
brew services start mysql

Perhaps you prefer postgresql:

brew install postgresql
brew services start postgresql