Local Development Setup on macOS

For the better part of the last 10 years or so, I was using MAMP (or later MAMP Pro) for my local development workflow. During this time, my requirements changed quite a bit compared to when I started. This became apparent, when we were looking for ways to streamline and automate some of our maintenance workflows. While MAMP is a comparably “easy” solution to setup sites, which works quite well, it has some limits โ€“ especially when you want to automate and script things.

While researching, I stumbled across several different solutions, from the “do-everything-yourself and set up *everything* manually” to docker-based, to varying vagrant vagrants or Laravel Valet.

I never really took the time to finally look into all of them, and just kept everything as it was, still using MAMP. But last week, I was setting up my new MacBook Pro, and I thought there will never be a better time to give it a shot and see what I can optimize.

After reading through some of the docs and especially after reading this article by Jonathan Bossenger, I figured that while all of the solutions have their own pros and cons, Laravel Valet sounds like it fits my workflow the best.

Here’s what I ended up with, at least for now.

๐Ÿ“ The Requirements

  • I have my sites stored in one place (~/Development/vhosts/...)
  • I don’t want to have to do all sorts of configurations for each site I install. Most of the sites I’m working with are basic WordPress installs and quite similar to each other
  • I want to be able to install and migrate (or just migrate if already installed) the current state of a live site, fast!
  • I want to be able to install a new site quickly without much setup, and this should be scriptable from the command line
  • I want to be able to install WordPress plugins and themes with the same easy install script
  • I want the ability to check out some of the plugins/themes from our git repos, instead of copying them from the live site
  • I want to be able to switch between php versions easily, bonus points if this could be defined per site, but I can live with a global switch
  • I want to use Mailhog to catch all mails from local installations

๐Ÿงช The Ingredients

  • macOS Monterey 12.1 (on M1 Apple Silicon)
  • Homebrew
  • Composer
  • valet+ โ€“ a fork of Laravel Valet (needed a bit of a workaround to get it running, see below)
  • wp-cli
  • wp valet โ€“ custom cli command
  • Custom Migration & Installation Scripts
  • SSH access to the live servers
  • SSH access to the git repos

๐Ÿ—„ The Directory Structure

Not that it really matters โ€“ you could structure your setup completely different than mine and it should work exactly the same โ€“ but for the sake of documentation, here’s how I structure my Development directories:

  • ~/Development
  • ~/Development/scripts
  • ~/Development/vhosts

scripts โ€“ contains all my dev scripts, like the ones to install and migrate live servers to local and stuff like that.

vhosts โ€“ contains the actual directories for each of the sites I’m working on

๐Ÿ“ฆ Installing Valet+

Laravel Valet is a minimalist development environment for macOS. It was initially developed by & for Laravel, but it’s perfectly suited for WordPress Development as well. Valet+ is a fork by weprovide, which adds some useful features, like XDebug support, easily switching between PHP versions or Mailhog.

I pretty much followed the installation instructions, which I copied here and added my comments in brackets:

  1. Install Homebrew or update to the latest version using brew update.
  2. Add the Homebrew PHP tap for Valet+ via brew tap henkrehorst/php.
  3. Install PHP 7.3 using Homebrew via brew install valet-php@7.3 --build-from-source.
  4. Link your PHP version using the brew link valet-php@7.3 --force command.

    โš ๏ธ Sometimes you need to restart all terminal windows for the link to take effect after install. Especially if you just installed a homebrew PHP version for the first time.
  5. Install Composer using Homebrew via brew install composer.
    (This was spitting out some errors the first time I ran it, after the second time it worked ๐Ÿคทโ€โ™‚๏ธ)
  6. Install Valet+ with Composer via composer global require weprovide/valet-plus.
  7. Add export PATH="$PATH:$HOME/.composer/vendor/bin" to ~/.zshrc
  8. Run the valet fix command. This will check for common issues preventing Valet+ from installing.
    (This showed quite a few errors about uninstalled PHP versions, which I ignored as I only installed v7.3 and will install others later)
  9. Run the valet install command. Optionally add --with-mariadb to use MariaDB instead of MySQL This will configure and install Valet+ and DnsMasq, and register Valet’s daemon to launch when your system starts.
    (Would be cool to be able to do it without the daemon and only launch when needed, but that’s ok for now)

    ๐Ÿ˜ตโ€๐Ÿ’ซ Now, at this point it was spitting out a whole red wall of errors to my command line. Read on below on how to get those fixed.
  10. Once Valet+ is installed, try pinging any *.test domain on your terminal using a command such as ping -c1 foobar.test. If Valet+ is installed correctly you should see this domain responding on If not you might have to restart your system. Especially when coming from the Dinghy (docker) solution.

The big red wall of an error in step 9 nearly made me give up, but after reading them closer, they turned out solvable. At least as a workaround. Here’s how I got valet install to run properly.

๐Ÿ‘ฉโ€๐Ÿ”ง Fixing valet install

1. APCU installation location not found

After reading through the errors closely and investigating some GitHub issues that sounded like a similar problem, I found out that PHP seems to search for a library called PCRE/PCRE2 (Perl Compatible Regular Expressions) in the wrong location. To make it work I had to add Symlinks like this. I added those for every PHP version I installed, even though I’m actually not sure if all of them use this library.

ln -s /opt/homebrew/include/pcre2.h /opt/homebrew/Cellar/valet-php@7.3/7.3.27_2/include/php/ext/pcre/pcre2.h

2. GeoIP not found

Now that the APCU thing was running through, the next wall of errors was caused by a GeoIP module. According to some comments on Github this is not really used anymore, and the easiest workaround seemed to be deactivating it for all PHP versions I use. I know, I know, not the prettiest thing to change those files directly, but it works for now and I hope this gets fixed in a future version.

To deactivate GeoIP, we need to set some parameters to false for each PHP version by changing this file:

nano ~/.composer/vendor/weprovide/valet-plus/cli/Valet/Pecl.php

In there, we need to set GeoIP to false, like this:

    self::GEOIP_EXTENSION => [
        '8.0' => false,
        '7.4' => false,
        '7.3' => false,
        '7.2' => false,
        '7.1' => false,
        '7.0' => false,
        'extension_type' => self::NORMAL_EXTENSION_TYPE,
        'brew_dependency' => 'geoip'

After those two changes, I ran the valet fix and valet install commands again, and they ran through without any errors:

valet fix && valet install

๐Ÿ“ฃ Register my vhosts directory with valet

Sites can now be added either manually, or โ€“ and this is what I’m going to do โ€“ I can register my whole vhosts directory by using the park command. To register it, I need to navigate to the vhosts directory and run:

valet park

Now all directories inside of vhosts will automatically be accessible from a web browser at <directory name>.test (or another domain ending you chose, see next step).

๐Ÿšฉ Changing the domain ending

I like to have my local sites under <name>.localhost, instead of the default <name>.test that valet uses. Luckily it’s easy to change this by running the domain command:

valet domain localhost

๐Ÿ‘ฉโ€๐Ÿ’ป Installing the wp valet cli command

To quickly spin up a new site from the command line there’s even a command for WP-CLI. Install the wp valet command like this:

wp package install aaemnnosttv/wp-cli-valet-command:@stable

๐Ÿช„ Installing a new site

Ok, now that everything is setup and running, all I have to do to create a new site is navigating to my vhosts directory and run the following command (replacing <name> with a name for the site):

wp valet new <name>

The command also lets me set a lot of parameters for the WP Install. For example, I could install WP version 5.8.3 like this. You can find all the options for the command here.

wp valet new <name> --version=5.8.3

This will do the following things:

  1. Create a new directory with the name provided
  2. Creating a new database named wp_<name>
  3. Download and install WordPress
  4. Create an SSL certificate
Et voilร : Creating a new local site only takes seconds.

๐Ÿ’ฃ Removing a site

The following command removes a site completely, including all its files & the database:

wp valet destroy <name>

๐Ÿ˜ Changing PHP versions

If you want to let valet run with a different PHP version, you can do so with the use command, this will download and install (if not already installed) the specified version and restart valet to use this version:

valet use php@8.0

โœ‰๏ธ Catching mails with Mailhog

I was using Mailhog before to intercept all mails going out from local installations. This is important to prevent local sites from sending emails, especially when working with client sites.

Luckily, Mailhog comes already preinstalled and configured with valet-plus, and the mails can be accessed by opening:


๐Ÿ‘ Wrapping up

As you can see, valet does a lot of cool things out of the box, and makes setting up local sites as easy as it can be for me. Apart from what I’ve shown here, valet should also make your sites accessible from your local network or even lets you share them publicly, which is awesome for testing on other devices. I still have a list of things to try and am still working on some scripting to completely automate the process of setting up and migrate live sites, but so far I really like how it works.

I guess the only thing that I’m missing, is that it exclusively runs Nginx, so I might still need another solution for when I really need something tested on Apache. But other than that it fits my workflow quite perfectly!

I wrote this guide mainly to document the process for my future self, when I have to do this again, but maybe it’s helpful for others as well.