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, /usr/local/etc/php/7.1/conf.d/ext-igbinary.ini, /usr/local/etc/php/7.1/conf.d/ext-redis.ini, /usr/local/etc/php/7.1/conf.d/ext-xdebug.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:
- PhpStorm for heavy lifting
- Visual Studio Code for a more nimble but still powerful IDE
- Sublime Text 3 because it’s insanely fast
- Atom for when I don't need the IDEs and I don't want to open sublime. No real reason
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.
.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
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); @ini_set('display_errors',0); 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.
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
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
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
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
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:
- generating a full backup (files and database) that is saved in the FTP which I can then download and unzip
- cloning the repository of the entire site and acquiring the database via some other means
- creating a backup of the site through the hosting dashboard, and then downloading that
- downloading all the files via FTP and getting the database some other way
“Getting the database some other way” can be one of these:
- connecting to the database with Sequel Pro and then manually exporting it and downloading it to the current computer
- 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)
- 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
I’ll then modify the
wp-config.php to match my environment. I’ll always change:
- db user
- db password
- db table name (
- 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';
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:
- I need Valet to know where to find this site, and
- I need the site to know where it is
Let’s say the project I’m working on is called
For 1), in the
~/Sites/rescuekittens, I’ll link it with valet:
$ valet link rescuekittens
That will mean the site will be accessible through
Next I’ll use SSL:
$ valet secure rescuekittens
And now I have
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:
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 | ✘ | j[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
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/hostsfile, 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.