Here’s why you should consider deploying WordPress with Vagrant for development and testing – and how to do it! I throw in some Composer for WordPress and wp-cli configuration as a bonus.
If you are actively developing your platform, or develop plugins or themes then you can quickly run out of installs to try things out on. You might hear of a bug that only seems to show itself with a particular version of WordPress or peculiar combination of plugins. Or perhaps you’d like to compare three or four themes side by side. When you look around your server space, though, all of your available installs are spoken for.
You can do a lot on a good host with subdomains, but it’s still a pain clicking through all the install steps, and standing up new databases, generating the .htaccess
file, fixing up various configuration options.
When I need a WordPress environment for development, I grab a skeleton setup – which is really a couple of directories and files, I tweak some names, and then run vagrant up
. Then it’s time to make coffee while Vagrant sets up a Linux virtual machine, downloads and installs the packages I’ve specified, sets up the MySQL database, installs the version of WordPress I need, grabs and activates my chosen theme and plugins, handles configuration, and manages URL rewriting.
If you’ve worked with WordPress for a while, you’ll know that none of that is particularly hard work but can be a drag – especially if you have to do it often.
Vagrant
So what’s all this about Vagrant then? Vagrant is really just a management layer that works with your favourite virtualization package (probably VirtualBox). Because it uses virtualization, you don’t need to worry about hosting, but you can still work with an instance of WordPress on a platform that’s near-identical to your public server. Not only that, but Vagrant can mount directories from your local computer (the so-called host machine) on the virtual OS (the guest machine). That means that you can use your local development tools without the need for uploading or installing.
In order for all this easy stuff to work, you’ll need hardware that supports virtualization (most modern computers do) and Vagrant installed. The installation processes for both Virtualbox and Vagrant are pretty easy these days.
Once Vagrant is installed on a machine, I usually add a Vagrant plugin: vagrant-hostmanager. This configures hostnames for you so that you can use something more friendly than an IP address to access your website. Adding a plugin to vagrant is simple:
$ vagrant plugin install vagrant-hostmanager
Setting up the environment
Made it through the installation phase? Great.
So here’s the skeleton I’m going to build. It consists of two mostly empty sibling directories dev
and infrastructure
.
./dev
./dev/composer.json
./infrastructure
./infrastructure/happydev.conf
./infrastructure/setup.sh
./infrastructure/Vagrantfile
Let’s begin with infrastructure
. This is where we’ll put the files for managing and configuring the Vagrant build. Vagrantfile
is the heart of this process – it tells vagrant where to get its virtual machine, which directories to mount, and all sorts of other stuff. Luckily, you can generate a vanilla Vagrantfile
and only edit what you need to customise. Let’s do that. First, I’ll set aside my Vagrantfile so that I can generate a new one for you.
Now to generate the Vagrantfile
:
$ cd infrastructure
$ vagrant init
A `Vagrantfile` has been placed in this directory. You are now
ready to `vagrant up` your first virtual environment! Please read
the comments in the Vagrantfile as well as documentation on
`vagrantup.com` for more information on using Vagrant.
As the output of the init
command says – the generated file is full of useful comments. I’m going to stick to what I need to add or alter.
Now let’s edit Vagrantfile
. There’s one line we’ll need to replace. I look for:
config.vm.box = "base"
and replace it with a reference to a virtual machine. There are plenty of directories available online. I want to use CentOS, which is a nice stable distro and part of the RedHat family. I look at Vagrant’s own resource for boxes – and find one that will work for me. I edit the config.vm.box
line.
config.vm.box = "bento/centos-6.7"
If you chose a different distro and you’re following along be aware you’ll probably need to amend the setup script I describe below in order to use the right package management commands for your OS.
This is enough to get a working box but let’s add some more configuration before we proceed.
config.ssh.forward_agent = true
config.vm.synced_folder "../dev", "/var/www/happydev"
config.vm.hostname = "happydev.vagrant.internal"
config.hostmanager.enabled = true
config.hostmanager.manage_host = true
config.vm.network :private_network, ip: "192.168.33.43"
config.vm.provision "shell", path: "setup.sh"
Let’s break that down. config.ssh.forward_agent
(line 1) will magically import your ssh-agent settings into the box enviornment – that means that your keys, for example, will work from within the context of the box – very handy if you’re checking stuff out from git.
Then I use config.vm.synced_folder
so that a directories on the local filesystem (relative to the infrastructure
directory) will be mounted on the guest box.
In the next block of directives, I set up networking. I specify a more or less arbitrary hostname and IP address. If you haven’t installed hostmanager plugin, you’ll need to comment out the config.hostmanager.*
lines – or your deployment will fail with an error – and you’ll need to configure your own `/etc/hosts</code file (or equivalent).
Finally, I use config.vm.provision` to tell vagrant to run my install script: `setup.sh` And that’s it for Vagrantfile.
Later on, I’ll have a full install script in setup.sh – but I’ll replace that for now with a placeholder script:
echo hello;
We can ignore happydev.conf
for now – it contains basic apache configuration and we’ll copy it into place on the guest box later on.
Let’s kick things off then.
$ vagrant up
For the first time round, you’ll need to wait while Vagrant downloads the box. It will cache it, though, so future deployments will be faster.
Here’s an edited version of my up
command’s output:
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Importing base box 'bento/centos-6.7'...
...
==> default: Machine booted and ready!
==> default: Checking for guest additions in VM...
==> default: Setting hostname...
==> default: Configuring and enabling network interfaces...
==> default: Mounting shared folders...
default: /vagrant => /home/mattz/scrap/happydev/infrastructure
default: /var/www/happydev => /home/mattz/scrap/happydev/dev
==> default: Updating /etc/hosts file on active guest machines...
==> default: Updating /etc/hosts file on host machine (password may be required)...
==> default: Running provisioner: shell...
default: Running: /tmp/vagrant-shell20170404-12521-1cxro40.sh
==> default: hello
So, we can see it has followed my instructions. It has mounted two directories (infrastructure
and dev
), configured my host, and run my set up script (that’s the hello
at the end).
Let’s log on to the box and take a quick look.
$ vagrant ssh
I am logged in to the server. And I can see my WordPress development directory:
[vagrant@happydev ~]$ ls -al /var/www/happydev/
total 12
drwxrwxr-x. 1 vagrant vagrant 4096 Apr 4 16:58 .
drwxr-xr-x. 8 root root 4096 Apr 4 16:57 ..
-rw-rw-r--. 1 vagrant vagrant 1266 Apr 4 16:15 composer.json
Provisioning
In fact, Vagrant thinks we have already provisioned this box – that’s because I specified the script setup.sh
which it duly ran. In order to get Vagrant to run a more involved script I could destroy and recreate the box.
$ vagrant destroy
$ vagrant up
This is a time consuming way of going about things – though I do it occasionally to check that everything still works from scratch. More often though, it’s enough to tell Vagrant to re-provision the box.
$ vagrant provision
So what should I have setup.sh
do? The answer is – everything else. Here is my setup script broken up into chunks and annotated.
First, some configuration:
VAGRANTDIR=/vagrant
SERVERDIR=/var/www/happydev
APACHECONF=happydev.conf
WPCLI=/usr/local/bin/wp
URL=happydev.vagrant.internal
TITLE=happydev
ADMINUSER=vagrant
ADMINPASS=vagrant
ADMINMAIL=vagrant@happydev.vagrant.internal
DBNAME=happydev_vagrant
DBUSER=vagrant
DBPASS=vagrant
These should be pretty self-explanatory. Note that the URL
matches the hostname I configured in Vagrantfile
and that SERVERDIR
matches the mounted directory on the guest box. There is nothing magic about any of these variables – they are not related to Vagrant at all. They are internal to the script and allow me to configure new boxes in a relatively central location without too much find/changing.
Next a bit of housekeeping:
die() {
echo $1;
exit 1;
}
Just a convenience function for killing the script if necessary. Next, I install various core packages.
sudo rpm -Uvh https://mirror.webtatic.com/yum/el6/latest.rpm
yum install -y php70w php70w-opcache
sudo yum -y remove mysql-libs
sudo yum -y install mysql-server
sudo yum -y install httpd;
sudo yum -y install php70w
sudo yum -y install php70w-mysql
sudo yum -y install php70w-xml
CentOS uses Yum as its package manager. By default, it isn’t very adventurous with PHP versions – so the webtatic
repo is a must. After installing that I’m able to install PHP 7, and some associated extensions.
Next: Composer:
cd /tmp
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/bin/composer
cd $SERVERDIR
composer update
I get Composer – then I change directory to $SERVERDIR
– in this case /var/www/happydev
and run composer update
. Of course that’s where the magic of Composer and and wpackagist.org come into play. This stage will get me the WordPress core files and any plugins and themes I need. You can read more about installing and setting up WordPress with Composer in an earlier post.
I prevent irritating warnings about date and time by adding a timezone to php.ini
:
sudo sed -i 's^;[ ]*date.timezone =[ ]*.*^date.timezone = Etc/UTC^' /etc/php.ini
and move on to the database:
sudo service mysqld start
/usr/bin/mysqladmin -u root password 'vagrant' || echo " -- unable to create pass - probably already done"
/usr/bin/mysqladmin -u root -h $URL password 'vagrant' -pvagrant || echo " -- unable to create pass - probably already done";
echo "CREATE DATABASE IF NOT EXISTS $DBNAME" | mysql -u root -pvagrant
echo "GRANT ALL PRIVILEGES ON $DBNAME.* TO '$DBUSER'@'%' IDENTIFIED BY '$DBPASS' WITH GRANT OPTION" | mysql -u root -pvagrant
echo "GRANT ALL PRIVILEGES ON $DBNAME.* TO '$DBUSER'@'localhost' IDENTIFIED BY '$DBPASS' WITH GRANT OPTION" | mysql -u root -pvagrant
echo "FLUSH PRIVILEGES" | mysql -u root -pvagrant
I start mysql, and create the database and user specified in the configuration section.
Time for Apache:
sudo cp $VAGRANTDIR/$APACHECONF /etc/httpd/conf.d/
I copy across the apache configuration file. This is a very basic apache set up which establishes the host and a web-facing directory at /var/www/happydev/site
.
Next I move on to WordPress at last:
mkdir -p ~/.wp-cli
echo "apache_modules:
- mod_rewrite" > ~/.wp-cli/config.yml
./vendor/bin/wp core config \
--dbname=$DBNAME \
--dbuser=$DBUSER \
--dbpass=$DBPASS \
--path=site \
--force || die "wp-cli core install failed"
# install the database tables
./vendor/bin/wp core install \
--url=$URL \
--title=$TITLE \
--admin_user=$ADMINUSER \
--admin_email=$ADMINMAIL \
--admin_password=$ADMINPASS \
--path=site || die "wp-cli core install failed"
# set rewrite structure
./vendor/bin/wp rewrite structure /%postname%/ --hard --path=site
./vendor/bin/wp theme activate onepress --path=site
Thanks to Composer I have access to wp-cli. You may have read a few posts about this amazing tool already on this blog – one about basic setup with wp-cli, another about using it for migrating the database, and a third about importing and exporting posts. I use it here for performing the install and configuration and then for generating the .htaccess
file to handle URL rewriting.
The Composer file
I skirted a little over that composer update
stage earlier. Remember that Composer is a dependency management system. It installs what you tell it to, as well as anything needed by the packages that you specify. wpackagist.org
track and manage WordPress and its plugins and themes for us, so we can set up much of WordPress via a single composer.json
file (though we still need to rely on tools like wp-cli
to manage local configuration and the database).
Here is the composer.json file I am using for this example.
{
"name": "getinstance/happyfun",
"description": "core for http://getinstance.com",
"authors": [
{
"name": "Matt Zandstra",
"email": "matt@getinstance.com"
}
],
"bin": ["bin/"],
"repositories": [
{
"type": "package",
"package": {
"name": "wordpress/wordpress",
"type": "webroot",
"version": "4.7.3",
"dist": {
"type": "zip",
"url": "https://github.com/WordPress/WordPress/archive/4.7.3.zip"
}
}
},
{
"type":"composer",
"url":"https://wpackagist.org"
}
],
"require": {
"wordpress/wordpress": "4.7.3",
"fancyguy/webroot-installer": "1.0.0",
"wp-cli/wp-cli": "1.1.0",
"wpackagist-theme/onepress": "1.3.0",
"wpackagist-plugin/akismet":"dev-trunk"
},
"extra": {
"webroot-dir": "site",
"webroot-package": "wordpress/wordpress",
"installer-paths": {
"site/wp-content/plugins/{$name}/": ["type:wordpress-plugin"],
"site/wp-content/themes/{$name}/": ["type:wordpress-theme"]
}
}
}
I specify WordPress 4.7.3 along with a theme and a plugin as proof of concept.
And now.. WordPress with Vagrant – I hope
Well, here goes nothing!
vagrant provision
Vagrant generates a LOT of verbosity at this point – passing through the output from all the commands in setup.sh
. If I’m lucky the process ends without error. You should design your setup script so that it can run harmlessly multiple times – that way missteps can be corrected by running provision
again (and again). Vagrant supports tools for provisioning such as ansible that you might find better suited to this job. I have always found a quick and dirty shell script adequate for my purposes.
The moment of truth
It may seem that I’ve put in a lot of work here – but remember, this is a script – I can use it over and over again with very little alteration.
Let’s see whether I have a running WordPress install. I point my browser at http://happydev.vagrant.internal
.
And there it is! I am ready to start coding. Next time I want to launch this environment I just need to run
vagrant up
and thirty seconds later the site will be good to go.