/ workflow

My local development environment

There are so many tools to do local development, no wonder folks are lost. I’ve mostly tried them all with the notable exception of Docker. Here’s what I ended up with on a mostly permanent basis:

Server, database, php, wp-cli

I develop on a Mac. This means I have homebrew, which is pretty much one of the pillars of my development flow.

Through brew I installed (and keep updated) the latest PHP and mariadb (essentially MySQL). Installing those are as easy as

$ brew install php71 mariadb

The other pillar of my development is composer. Installing that allows me to globally install a bunch of packages that I reuse, plus it comes in handy when working with some of the sites (see later).

For the server, my choice is Laravel Valet. It's fairly easy to install, but you need to have both homebrew and composer on your computer already.

Oh, and make sure to get wpcli. It is truly an AMAZING tool. Makes life so much easier!

Extra stuff: redis / elasticsearch

Some of the sites I work on require these. They can be installed through homebrew:

$ brew install redis elasticsearch


Super important! Basically I don’t want any of the sites to send emails out to the internet, so I want to catch them all!

Mailhog is to emails like Ash Ketchum to all the Pokemon.

Predictably, you’ll get it through homebrew:

$ brew install mailhog

And then you need to edit some details in the php ini:

$ php --ini
Configuration File (php.ini) Path: /usr/local/etc/php/7.1
Loaded Configuration File:         /usr/local/etc/php/7.1/php.ini
Scan for additional .ini files in: /usr/local/etc/php/7.1/conf.d
Additional .ini files parsed:      /usr/local/etc/php/7.1/conf.d/ext-blackfire.ini,

That will tell you where the loaded configuration files are. We need the base php.ini, so let’s edit that one:

$ subl /usr/local/etc/php/7.1/php.ini

That will open the file. Then change this part of the ini:

; For Unix only.  You may supply arguments as well (default: "sendmail -t -i").
; http://php.net/sendmail-path
sendmail_path = sendmail


; For Unix only.  You may supply arguments as well (default: "sendmail -t -i").
; http://php.net/sendmail-path
sendmail_path = mailhog sendmail [email protected]

And then restart php with

$ valet restart


For editors, I use a combination of these:

For database stuff, I use Sequel Pro. There’s also Navicat Premium, which is the professional grade high-end solution to talk to most of the databases, but I can’t justify the price tag ($1,299).

For moving files around, I tend to prefer them being in git and then auto-deployed to the server through either the hosting service provider (huge shoutout to Pantheon here!), or through deploybot.

If I do need to touch an FTP program, then it's Transmit. It’s just great, and can connect to literally anything.


I only use git. I don’t touch svn or mercurial. That keeps me from contributing to WordPress core, but hey...

Configurations to take care of first

1. valet local domain

By default the local development domain Valet uses is .dev. Due to some recent change to the publicly available top level domains, .dev is no longer recommended as a tld to use.

I chose .test. Change it like so once valet is installed:

$ valet domain test

It’s best to do this before you set up any local domains.

2. wpcli configurations

There are some baseline things that I add to all my sites when I set them up. Here’s my ~/.wp-cli/config.yml file:

core config:
	dbuser: root
	dbpass: root
	dbhost: localhost
	dbprefix: wp_
	locale: en_GB
	extra-php: |
		define('WP_DEBUG', true);
		define('WP_DEBUG_LOG', true);
		define('WP_DEBUG_DISPLAY', false);
		define( 'WCS_DEBUG', true );
core install:
	admin_user: javorszky
	admin_password: <password>
	admin_email: [email protected]

Read more about wpcli configuration files through here.

This makes setting up sites a lot faster.

3. ssh keys

I have an rsa and an ed25519 key. The latter is more secure, faster, but fewer things support it. Thankfully most modern servers can deal with it, and Github too. I think this is the article I followed when generating my new keys.

4. folders

I have a ~/Sites folder in which I put all of the sites I work on. It makes working easy.

Fantastic Sites and Where to Find Them.

5. database naming scheme

My localhost mariadb has a lot of databases. One for each site. To keep track of what’s what I have a naming scheme. It’s mostly a form of <purpose>.<sitename>.

So for my plugins that I work on internatlly, or my own test sites that I use to play around with, I use dev as purpose. If the site is in ~/Sites/dev folder, then the database would be called dev.dev.

For client work where I had to set up a copy of the site locally, I’ll prefix it with client. So say I have a client called ACME Inc, and their local site lives in ~/Sites/acme, the database name would be client.acme.

If an agency recruits my help with several of their sites, I'd use agencyname as prefix. So if the site is in ~/Sites/furnitureunlimited (not an actual site), then the db name would be agencyname.furnitureunlimited.

And so on.

Setting up the site for work

Depending on what the task is, here’s how I structure my work:

1. We need some special functionality that doesn’t exist yet

If the environment they are working with can be set up without using their actual site, then I’d just use my dev site.

For example requests that ask for a different way to handle / display Subscriptions on WooCommerce that needs to work together with a few plugins, then I’d get all the plugins I’m missing, and set up the site as close to theirs. This would include products, their type, tax settings, shipping settings, subscription settings (recurrence, synchronisation). Basically I try to mirror the site in all ways that are important to the requested feature at hand.

There’s no reason to mirror the tax settings if the feature requested is around the user changing when to have their next renewal order charged.

I'd then set up an empty private repository on my own Github account, or ask them to set one up for themselves and add me as a full collaborator. Then I’d use a plugin base. Currently I’m working with and improving this one: Mindsize/wp-plugin-base.

I’ll then modify that plugin, and commit / push as needed. The end result is then a link ot the repository where they can clone / download / raise issues, and so on.

2. We need you to debug this issue we’re having on our live site

This involves cloning the entire site, files and database. Depending on the hosting, this can be one of the following:

  1. generating a full backup (files and database) that is saved in the FTP which I can then download and unzip
  2. cloning the repository of the entire site and acquiring the database via some other means
  3. creating a backup of the site through the hosting dashboard, and then downloading that
  4. downloading all the files via FTP and getting the database some other way

“Getting the database some other way” can be one of these:

  1. connecting to the database with Sequel Pro and then manually exporting it and downloading it to the current computer
  2. if hosting company provides ssh access AND has wp-cli, then issuing a wp db export filename.sql, then compressing that, and then using either FTP to download that, or scp (the latter is usually faster)
  3. if hosting company provides database access through phpmyadmin (khm, WPEngine...), then logging in there, and exporting through them and hoping it succeeds

I would create a folder for the files within ~/Sites, and copy all the files (inlcuding WP Core) into that folder.

I’ll put the exported, downloaded, and unzipped raw .sql file in the root directory next to the wp-config.php file.

I’ll then modify the wp-config.php to match my environment. I’ll always change:

  • db user
  • db password
  • db table name (client.foldername)
  • all the salts
  • debug constants
  • remove some hosting specific constants, or bypass them

Next up I'll create the database table through wpcli

wp db create

And then import the sql file to that. I can do that through wpcli, but I prefer going through pv, which is a package you can install through homebrew with brew install pv.

pv export.sql | mysql -uroot -proot client.foldername

This will get the contents of the database file in, and also show me a progress bar of what’s happening. This is useful for larger (1.5G+) database files, as I don’t have to wonder whether the transfer got stuck, or how much more time that needs.

I’ll change some of the settings later.

disabling WP functionalities without logging in

Next I will need to kill a bunch of data in the database because it is a live database, so it will have live customer info, payment tokens, and so on. To start with, I’ll remove all the scheduled actions, so no renewals will take place.

For that I open Sequel Pro, and issue the following query:

DELETE FROM wp_posts WHERE post_type = 'scheduled-action';

Assuming that wp_posts is the name of the posts table.

Then, because I have mailhog, I don’t need to install a plugin called disable emails, because all of the emails will be captured. However on a public staging server, this is a must! (btw, see my article on disabling emails in WordPress on why that particular plugin was chosen).

Then I’ll create a user account for myself on the site via wpcli if I don’t have one already on the site I copied:

$ wp user create javorszky [email protected] --role=administrator --user_pass=<some pass>

Before I can log in, I’ll need the site to be accessible. That’s a two-pronged approach:

  1. I need Valet to know where to find this site, and
  2. I need the site to know where it is

Let’s say the project I’m working on is called rescuekittens.

For 1), in the ~/Sites/rescuekittens, I’ll link it with valet:

$ valet link rescuekittens

That will mean the site will be accessible through http://rescuekittens.test.

Next I’ll use SSL:

$ valet secure rescuekittens

And now I have https://rescuekittens.test.

For 2), I need to change all the old domains into my development one. I will then therefore do

$ wp search-replace https://rescuekittens.org.uk https://rescuekittens.test

And then I should be good to log in.

Disabling more WP functionalities through logging in

First up is payment gateway tokens. For Stripe, I’ll start by clearing the live tokens on the local development site, saving the option (so I won’t have access to those going forward), then ticking the box to use Stripe test, and then changing those test keys to my own test keys in Stripe. That’s important in case I need to debug checkout with regards to what Stripe knows about an order vs what WooCommerce knows about an order.

I’ll also find all other plugins that use some form of tokens and clear / change those. They will inclue:

  • shipstation
  • sendgrid
  • mailchimp

I don’t want to accidentally send emails through the client’s account.

I’ll also make sure to change settings of things like ElasticPress and Redis. Those ones I’ll point at my own installs and give them a unique prefix so they store this site’s data separate to all other local site’s data.

Then I’ll disable most tracking scripts like Google Analytics, Mixpanel, GoSquared, Piwik, hubspot, and so on. I don’t want my actions to pollute the analytics of my client.

Then I’ll go over the functionality again, and disconnect / disable / change settings for all other plugins that I didn’t remember to include here.

At this stage, depending on what the problem is, I may request access to additional tools.

And NOW I can start working.

3. When I need to spin up a completely new site

Currently I’ll create a new folder in ~/Sites, and then within that folder, I use wpcli:

 13:15:15 | [email protected] | ~ |
$ cd Sites

 13:15:16 | [email protected] | ~/Sites |
$ mkdir article

 13:15:18 | [email protected] | ~/Sites |
$ cd article

 13:15:20 | [email protected] | ~/Sites/article |
$ wp core download
Downloading WordPress 4.8.3 (en_US)...
md5 hash verified: 4a8c83d449351c76a8d5bea7d42710a2
Success: WordPress downloaded.

 13:15:32 | [email protected] | ~/Sites/article | 10s |
$ wp config create
Error: Parameter errors:
 missing --dbname parameter (Set the database name.)

 13:15:36 | ✘ | [email protected] | ~/Sites/article |
$ wp config create --dbname=dev.article
Success: Generated 'wp-config.php' file.

 13:15:44 | [email protected] | ~/Sites/article |
$ wp db create
Success: Database created.

 13:15:47 | [email protected] | ~/Sites/article |
$ wp core install
Error: Parameter errors:
 missing --url parameter (The address of the new site.)
 missing --title parameter (The title of the new site.)

 13:15:50 | ✘ | [email protected] | ~/Sites/article |
$ wp core install --url=https://article.test --title="article test"
Success: WordPress installed successfully.

 13:16:03 | [email protected] | ~/Sites/article |

I let wpcli nag me about missing config options. Those are always site-specific. The rest of the config options have been filled in from the ~/Sites/.wp-cli/config.yml file.

Then I’ll add necessary plugins, and create my new plugin in the wp-content/plugins folder, and then off to code I am.


Hopefully this helps y’all in finding out how you want to do your development. This not “the best way” to do it, but it’s a way I found useful for my needs.

I want to touch on a coupl of “but why not”s I imagine people might have:

  • “but why not VVV?” - because I don’t like Virtualbox, having to install all of those, and provisioning them takes FOREVER, and I’m not familiar with their configuration, so customizing it is a pain
  • “but why not Docker?” - Docker is dark voodoo magic. It’s not beginner friendly, it takes a while to figure out how to do, I don’t understand it, and I don’t want to put in the time to understand it unless I actually NEED to use it. Which I don’t :)
  • “but why not MAMP?” - I used it. Was great. Still you need to install an app, it messes with ports, you need root access to use port 80, you need to mess with the /etc/hosts file, and adding things like mailhog, xdebug, elasticsearch, redis is not trivial
  • “but why not Local by Flywheel?” - I don’t like Flywheel. Their incredibly agressive sales team for their reseller account put me off. Also Local is just a coat of paint over docker / virtualbox (I honestly don’t know which...)
  • “but why not Chassis?” - see my answer at VVV
  • “but why not <some other thing>?” - it’s probably a hipster thing and I haven’t heard of it :D

As always, if you have any questions, reach out to me over on the new and improved, 280 character long twitters! Click this link to tweet @ me.

Photo by Todd Quackenbush on Unsplash.

Gabor Javorszky

I'm a freelance WordPress / WooCommerce developer focusing on Subscriptions and advanced functionality. Get in touch: @javorszky / gabor (at) javorszky (dot) co (dot) uk. Read more on the About page.

Read More