WordPress and I have had an on-again, off-again relationship for years. I recently decided to pick the CMS up again after Luke Brown introduced me to the BedRock WordPress Stack from Roots. For web development, Ruby on Rails is my framework of choice. Inspired by The 12-Factor App methodology, the team at Roots have managed to turn development and deployment of a WordPress site into one that is more like a using a web application framework. I like this because that is how I like to work in Rails. Thanks to BedRock, I can work this way in WordPress too (… almost)!
I find that when starting with BedRock, I make consistent changes before I can start to actually work. I thought that I’d share those. By doing this, I’m working through the process for myself as I write. If the changes still make sense when writing this article is done, I may put in a pull request to the project.
I’m going to take the scenic route and talk about my setup first. Once that’s done, we’ll dive in to BedRock.
Here we go.
My development operating system is Ubuntu 12.04 LTS (upgrading to the 14.04 LTS in a few days) and I edit using SublimeText 3. Before starting a new WordPress project, I’ll commonly have the following tools installed:
Here is how I set each of them up. I work in the terminal all the time so this article is “terminal-heavy”. When you see blocks of text looking like the block below that start with a $
, I mean for you to type in to the terminal command prompt and then hit the Enter key:
$ date
If there is anything that contains a #
, this is a comment. You can ignore these lines. If you happen to copy the #
and the text after it, everything including and after #
will have no effect.
$ cal # This is the date command
The version of Ubuntu I use will not always have the latest version of the tools I like to use. To add the latest versions, I use repositories in PPAs that are unofficially provided. Ubuntu will warn that PPAs should be used with caution. To date, I have had no problems using PPAs. Thankfully, the respective maintainers have been diligent. Nevertheless, the warning should be heeded, especially on production servers.
Before going to the next part, you want to check that you have python-software-properties
installed. This will help us add PPAs:
$ sudo apt-get install python-software-properties
SublimeText is an excellent text editor. I currently use SublimeText 3. To keep in sync with updates, I use an installer from WEB UPD8:
$ sudo add-apt-repository ppa:webupd8team/sublime-text-3
$ sudo apt-get update
$ sudo apt-get install sublime-text-installer
The installer takes care of configuration as well and sets up the command subl
to control SublimeText. Once completed, check the version installed using:
$ subl --version
Git is my version control system of choice. I use the PPA from the Ubuntu Git Maintainers team to stay up-to-date:
$ sudo add-apt-repository ppa:git-core/ppa
$ sudo apt-get update
$ sudo apt-get install git
To check the version installed, use:
$ git --version
For PHP, I use the PPA maintaned by Ondřej Surý:
$ sudo add-apt-repository ppa:ondrej/php5
$ sudo apt-get update
$ sudo apt-get install php5
To test which version you have installed use:
$ php --version
Since I’m using Nginx, I use PHP FPM to allow it to support PHP:
$ sudo apt-get install php5-fpm
$ php5-fpm --version
WP-CLI provides command-line functionality for a WordPress site. Being a command-line hermit, I have to admit, it’s pretty nifty. It would be a good idea to refer to the actual installation instructions for the latest setup procedure. Here’s what I did to set up WP-CLI on my machine.
Downloaded wp-cli.phar:
$ curl -O http://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
Then, checked that it worked:
$ php wp-cli.phar --info
Made it executable by typing wp
:
$ chmod +x wp-cli.phar
$ sudo mv wp-cli.phar /usr/local/bin/wp
For deployment (installing the project on a remote server), BedRock borrows a tool called Capistrano. Rails developers will be familiar with the tool. Capistrano is a an automatic deployment tool written in Ruby and works with different languages, in this case PHP. RVM is a tool that makes Ruby even more of a pleasure to work with. Let’s set it up RVM now so we can install Capistrano later:
$ \curl -sSL http://get.rvm.io | bash -s stable
After the installation, open a new Terminal and test that RVM installed correctly:
$ rvm --version
At this point, RVM is set up but we don’t have any Rubies installed.
$ rvm list
At the time of writing, the latest Ruby available is Ruby 2.1.2. We’ll use RVM to install it, following hints and instructions during the installation process. The process starts with:
$ rvm install 2.1.2
When the process has completed successfully, test that you have at least one Ruby installed:
$ rvm list
So far so good.
Instead of having a general Ruby setup on my machine, I use RVM with gemsets
to specify the exact Ruby setup I want for each project. We’ll see how gemsets
work in a little while.
Now that Oracle owns MySQL, MariaDB was developed as a “drop-in replacement” for MySQL. For now, this means that you can have MySQL or MariaDB installed and when actually using the database, you would not be able to tell the difference. They look exactly alike. You can read about the benefits of MariaDB over MySQL in a variety of places on the Internet.
I use the repository configuration tool for the steps for setting up MariaDB on my machine. I chose to use the 10.0 branch. Since I’m in Zambia, the tool chose the nearest mirror to me. The following commands installed the key and APT repository for me. It will likely look different for you:
$ sudo apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0xcbcb082a1bb943db
$ sudo add-apt-repository 'deb http://mirror.zol.co.zw/mariadb/repo/10.0/ubuntu precise main'
Then install MariaDB:
$ sudo apt-get update
$ sudo apt-get install mariadb-server
To check that it installed, you can use the mysql
command just like you would with MySQL:
$ mysql --version
Nginx is a web server. I switched to Nginx from Apache several years ago and prefer it because I get better resource usage out of it. Yet again, I use a PPA for it and install it thusly:
$ sudo add-apt-repository ppa:nginx/stable
$ sudo apt-get update
$ sudo apt-get install nginx
To check which version of Nginx you have installed use:
$ nginx -v
Finally, Composer is the tool that BedRock uses to pull all the dependencies for a WordPress project together. If you are a Ruby developer, you would no doubt have used Bundler. composer
is like bundler
for PHP.
$ curl -sS http://getcomposer.org/installer | php
$ sudo mv composer.phar /usr/local/bin/composer
If that was successful, you’ll be able to check the version using:
$ composer --version
Now that we have all the tools we need in place, we can start working with the BedRock WordPress Stack.
Let’s say we have a folder called Projects
and another called Repositories
in our home folder (~
). Let’s also say we have a client project called Jubilee that we’re working on and we have chosen to use WordPress to do it. The first two commands below will create a folder named Projects
and a folder named Repositories
if they don’t already exist:
$ mkdir -p ~/Projects
$ mkdir -p ~/Repositories
$ cd ~/Projects
To instantiate a WordPress project with BedRock, we use the following command. For reference and more up-to-date instructions, please have a look at the BedRock project on GitHub:
$ composer create-project roots/bedrock Jubilee
Composer will now fetch WordPress wrapped in some useful tools. Welcome to BedRock!
From the Projects
folder, let’s have a look at what that composer
command just did for us.
$ ls -a
. CHANGELOG.md config .env.example .gitignore scripts wp-cli.yml
.. composer.json CONTRIBUTING.md Gemfile LICENSE.md vendor
Capfile composer.lock .env Gemfile.lock README.md web
If you’ve only ever used WordPress from the download, many files will look unfamiliar. Let’s take a closer look:
$ cd Jubilee
$ subl .
This will open up the Jubilee
project in SublimeText. Some useful files to have a look at right away are composer.json
, Gemfile
, .gitignore
and .env
. Have a look for now, we won’t make any changes just yet.
BedRock uses Git for version control. From inside the Jubilee
folder, initialise the Git repository and make the first commit:
$ git init
$ git add .
$ git commit -m "Initial project import."
You will remember that a .gitignore
has already been defined in the project so not all of the files have been added. This is a useful to know. If we are working with other people on this project, when they clone it, their version will be missing some critical files that composer
fetched for us. Perhaps, it would be a good idea to let other developers on the team know how to get their setup to look exactly like ours. I work alone on my projects but think it’s a good idea to plan for the day I’ll have even just one more developer at Pencil Case Studios. As a habit or convention, I create a folder called _meta
to put example configurations and notes for other developers (and myself). Inside _meta
, I use README
for project notes and hints. TODO
is for tasks relating to this project. Let’s set that up:
$ git checkout -b meta
$ mkdir _meta
$ touch _meta/README
$ touch _meta/TODO
$ git add .
$ git commit -m "Added _meta/ stub."
Once we are done, we can commit the change to the master
branch and then remove the meta
branch:
$ git checkout master
$ git merge meta
$ git branch -d meta
So what would a freshly cloned repository look like? I’ll take you on a roundabout way of setting that up.
Let’s go in to our Repositories
folder and set up a Git repository that can be cloned from.
$ cd ~/Repositories
$ mkdir Jubilee.git
$ cd Jubilee.git
$ git init --bare
Now, we can go back to the Jubilee
folder and push
that in to the Jubilee.git
folder. After that, we’ll be able to clone
from Jubilee.git
. We’ll start by renaming Jubilee
:
$ cd ~/Projects
$ mv Jubilee Jubilee.original
$ cd Jubilee.original
$ git remote add origin ~/Repositories/Jubilee.git
$ git push origin master
We can now clone from Jubilee.git
like this:
$ cd ~/Projects
$ git clone ~/Repositories/Jubilee.git
Now, we should have a folder called Jubilee.original
(where we started our project). We’ll also have a folder called Jubilee
(our freshly cloned repository). This is what the two folders have inside them. You’ll notice that the contents are slightly different. The differences are even deeper than they appear and we’ll look more closely at that soon:
$ ls -a Jubilee*
Jubilee:
. CHANGELOG.md config Gemfile .gitignore README.md web
.. composer.json CONTRIBUTING.md Gemfile.lock LICENSE.md scripts wp-cli.yml
Capfile composer.lock .env.example .git _meta vendor
Jubilee.original:
. CHANGELOG.md config .env.example .git _meta vendor
.. composer.json CONTRIBUTING.md Gemfile .gitignore README.md web
Capfile composer.lock .env Gemfile.lock LICENSE.md scripts wp-cli.yml
You’ll notice that the file .env
in Jubilee.original
is missing from the newly cloned Jubilee
. BedRock, uses phpdotenv and .env
to load shell environment variables on a per-project basis. This is powerful because you should never store sensitive credentials in your code. In this case the sensitive credentials are things like the database username and password.
To help, .env.example
is kept in the repository while .env
is left out. I end up moving .env
and .env.example
in to the _meta
folder. To do this, we can:
$ cd ~/Projects/Jubilee
$ git checkout -b init
$ mkdir -p _meta/examples/env
$ cp ../Jubilee.original/.env _meta/examples/env/.env.dev
$ git mv .env.example _meta/examples/env/
I create multiple versions of .env
; one for each deployment environment I’ll be working in. This usually ends up being .env.dev
(for local development), .env.virtual
, .env.staging
and .env.production
. It is overkill but it helps me separate notes that I may need that are specific to a deployment environment.
A this point, changes have obviously been made to the repository. However, git status
will show that nothing has changed. This is because of this snippet from .gitignore
:
...
# Dotenv
.env
.env.*
!.env.example
...
Effectively, this keeps every file named “.env” and every file whose name starts with “.env.” (except “.env.example”) out of this Git repository. This is a more aggressive setup than we’ll need so let’s trim it down:
...
# Dotenv
.env
...
Now, we can commit the changes. You may want to leave a note in _meta/README
to explain the new location of the .env
s for when a new developer clones this repository.
Let’s look at the Ruby side to the BedRock project. Ruby uses gems to provide functionality to a project. It is possible to install gems system-wide. However, because different projects may require different versions of the same gem, it is useful to define project-specific installations of exactly the versions of the gems needed. RVM provides gemsets for exactly this.
Create a file called .ruby-version
at the top of the WordPress project:
$ cd ~/Projects/Jubilee
$ touch .ruby-version
In the file add the following line:
[email protected]
Now, whenever we change in to the Jubilee
folder, RVM will automatically switch to using Ruby 2.1.2 and the gemset jubilee-wp-bedrock
for this WordPress project. Go ahead and commit this change.
Have a look at the file Gemfile
. It defines which gems we need to use in this project. At the start, it looks like this:
source 'http://rubygems.org'
gem 'capistrano', '~> 3.1.0'
gem 'capistrano-composer'
The main gem we need here is capistrano-composer. Gems are able to define dependencies. It turns out that Capistrano is dependency of capistrano-composer
. As such, we can simplify the Gemfile to just:
source 'http://rubygems.org'
gem 'capistrano-composer', '~> 0.0.4'
The '~> 0.0.4'
allows us to lock the gem to a range of versions using the semantic versioning convention. The file Gemfile.lock
keeps a definition of what gems and versions our project is locked to. Since we’ve made a change to the Gemfile, we can update all the gem requirements for our project with one command:
$ cd ~/Projects/Jubilee
$ bundle update
If there are any gems that are no longer required, we can remove them using:
$ gem cleanup
We can also check if the gems in our project fall out of date using:
$ bundle outdated
These changes to the Git repository can now be committed.
The version of WordPress that BedRock downloads for us is defined in composer.json
. WordPress gets installed in to the web/wp
folder. You’ll notice that this folder is present in Jubilee.original
and missing in Jubilee
. This setup means that WordPress is actually a dependency in our project. It also makes quick work of updating to a new version when one is available.
At the time of writing, BedRock will download WordPress 3.9. Notice the following lines in composer.json
:
...
"name": "wordpress/wordpress",
"version": "3.9",
"type": "webroot",
...
"url": "http://wordpress.org/wordpress-3.9.zip"
...
"wordpress/wordpress": "3.9",
...
However, WordPress 3.9.1 is available. Let’s update composer.json
to make sure that WordPress 3.9.1 is the version that we use for our project. To do this, find and replace 3.9
with 3.9.1
in each of the lines we saw earlier:
...
"name": "wordpress/wordpress",
"version": "3.9.1",
"type": "webroot",
...
"url": "http://wordpress.org/wordpress-3.9.1.zip"
...
"wordpress/wordpress": "3.9.1",
...
I don’t particularly like this process of updating the WordPress version. I could easily change the version of a dependency I didn’t mean to change or miss one line. I think using composer require <namespace>/<packagename>
syntax is far more elegant and less error prone. Unfortunately, I have not yet been able to get it to work as my understanding of Composer is still quite shallow.
After composer.json
has been updated, the new WordPress can be automatically retrieved using:
$ cd ~/Projects/Jubilee
$ composer update
This will also update composer.lock
in a similar way to what we’ve seen with Gemfile
and Gemfile.lock
.
We can commit the changes to the Git repository at this point.
Notice also that other dependencies where installed or updated as well. These are placed in the vendor
folder. You’ll notice that the contents of vendor
were until this point present in Jubilee.original
and missing in Jubilee
.
At this point it is useful to highlight that this is what makes BedRock so powerful. With a very lean repository, we can define what we need and download or update as required. In the past I’ve had to lug around a complete repository full of unnecessary (out of date) themes, plugins and other dependencies. BedRock flips this from inconvenience to elegance.
Capistrano is the tool that BedRock uses to deploy a WordPress site. In the original Gemfile
, recall that capistrano
was defined (gem 'capistrano', '~> 3.1.0'
) and then we removed the line simplify the file.
This did not come with repercussions. In the earlier Gemfile
, we were guaranteed to have Capistrano version 3.1.0. Now, we’ll get the latest version of Capistrano available. I prefer this arrangement and right, now the latest version is Capistrano 3.2.1. You can check your version with:
$ cd ~/Projects/Jubilee
$ gem list | grep capistrano
Capistrano 3.2.1 is a little different than earlier versions so lets recreate the configuration for use with our project.
First we’ll remove the existing Capistrano configuration files and settings:
$ cd ~/Projects/Jubilee
$ mv Capfile ~/Desktop
$ mv config/deploy* ~/Desktop
As mentioned earlier, I like to have deployment environments called virtual, staging and production. We’ll use Capistrano, using the cap
command, to create a configuration for each deployment environment like so:
$ cap install STAGES=virtual,staging,production
This recreates the Capfile
, config/deploy.rb
and config/deploy/
with its respective files.
By default, Capistrano does not know about Composer. Since this is critical for BedRock to use during deployment, we’ll need to update the new Capfile
by re-adding a line that enables it to use Composer. Add the line require 'capistrano/composer'
so that your Capfile
ends up looking something like this:
# Load DSL and Setup Up Stages
require 'capistrano/setup'
# Includes default deployment tasks
require 'capistrano/deploy'
# Load tasks from gems
require 'capistrano/composer'
# Loads custom tasks from 'lib/capistrano/tasks' if you have any defined.
Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }
I prefer to have developers create their own Capistrano configuration similar to how they create their own .env
. As such, I create example configurations inside _meta/
for Capistrano as well:
$ cd ~/Projects/Jubilee
$ git checkout init
$ mkdir _meta/examples/capistrano
$ git mv config* _meta/examples/capistrano
With the example Capistrano configurations in place, we can make Git ignore the configurations where Capistrano expects to find them. In .gitignore
add the lines:
...
# Capistrano configuration
config/deploy.rb
config/deploy/
...
Next, add notes to _meta/README
to allow a new developer to the project to figure out how to get Capistrano set up. The setup involves copying the examples in _meta/examples/capistrano
to the config
folder and then customising the settings to fit their own environment.
It would be useful to commit the changes to the repository at this point.
We have not yet prepared Capistrano to actually deploy a WordPress site yet. We can do that now. Since we have a .env
already defined, lets allow Capistrano to access the variables in it. To do this, we’ll install the dotenv gem. Add the dotenv
line to Gemfile
:
source 'http://rubygems.org'
gem 'capistrano-composer', '~> 0.0.4'
gem 'dotenv', '~> 0.11.1'
To install the new gem, run bundle install
.
Next, let Capistrano know that it can access the contents of .env
using the dotenv
gem. Update Capfile
so that it now includes the dotenv
configuration:
# Load DSL and Setup Up Stages
require 'capistrano/setup'
# Includes default deployment tasks
require 'capistrano/deploy'
# Load tasks from gems
require 'capistrano/composer'
# Ref: http://github.com/bkeepers/dotenv
require 'dotenv'
Dotenv.load
# Loads custom tasks from 'lib/capistrano/tasks' if you have any defined.
Dir.glob('lib/capistrano/tasks/*.cap').each { |r| import r }
Now, if there was an environment variable in .env
named DB_NAME
, we can access it in our Capistrano configuration using ENV["DB_NAME"]
. I find it useful to add variables in .env
that Capistrano can use in deployment. For example:
# Application deployment
PROJECT_NAME=Jubilee
DEPLOY_USER=deploy
WORDPRESS_APPLICATIONS_FOLDER=/var/Sites/WordPress
# Virtual Deployment
VIRTUAL_DEPLOYMENT_PATH=virtual
VIRTUAL_REPOSITORY_DEPLOYMENT_BRANCH=master
VIRTUAL_SERVER_NAME=virtual_server_name # E.g. jubilee.virtualbox.local
# Staging Deployment
STAGING_DEPLOYMENT_PATH=staging
STAGING_REPOSITORY_DEPLOYMENT_BRANCH=master
STAGING_SERVER_NAME=staging_server_name # E.g. staging.testserver.co.zm
# Production Deployment
PRODUCTION_DEPLOYMENT_PATH=production
PRODUCTION_REPOSITORY_DEPLOYMENT_BRANCH=master
PRODUCTION_SERVER_NAME=production_server_name # E.g. jubilee.co.zm
These can be added to the example .env
configurations. The changes to the repository can be commited at this point too.
Now that we can set and read environment variables, it is a good time to use these in our deployment configurations. We’ll start with deploy.rb
in the example Capistrano configuration. Let’s update deploy.rb
to look like this:
# config valid only for Capistrano 3.1
lock "3.2.1"
set :application, ENV["PROJECT_NAME"]
set :deploy_user, ENV["DEPLOY_USER"]
set :wordpress_applications_folder, ENV["WORDPRESS_APPLICATIONS_FOLDER"]
set :scm, :git
set :use_sudo, false
set :log_level, :info
set :linked_files, %w{.env}
set :linked_dirs, %w{
web/app/mu-plugins
web/app/plugins
web/app/themes
web/app/uploads
}
namespace :deploy do
desc "Restart application"
task :restart do
on roles(:app), in: :sequence, wait: 5 do
# Your restart mechanism here, for example:
# execute :service, :nginx, :reload
end
end
end
# The above restart task is not run by default
# Uncomment the following line to run it on deploys if needed
# after "deploy:publishing", "deploy:restart"
Useful to highlight at this point are :linked_files
and :linked_dirs
. Capistrano deploys several versions of a project using Git so that it can rollback to previous versions if necessary. :linked_files
and :linked_dirs
get symbolically linked and so don’t need to be versioned with the rest of the project. I find it useful to use this setup for the WordPress plugins
, themes
, uploads
folders and and the BedRock .env
file.
With this understanding, let’s keep WordPress themes out of the repository. Edit .gitignore
by adding the following lines:
# Application themes
web/app/themes/*
!web/app/themes/.gitkeep
The deploy.rb
configuration defines the general Capistrano behaviour. For a specific deployment environment, for example the virtual deployment environment, we can update virtual.rb
inside _meta/examples/capistrano
. Update virtual.rb
so it looks something like this:
# Ref: http://gist.github.com/293302
server ENV["VIRTUAL_SERVER_NAME"], user: ENV["DEPLOY_USER"], roles: [:web, :app, :db]
set :deployment_path, ENV["VIRTUAL_DEPLOYMENT_PATH"]
set :deploy_to, "#{fetch(:wordpress_applications_folder)}/#{fetch(:application)}/#{fetch(:deployment_path)}"
# Don't forget to make this repo on the server
set :repository_server_name, ENV["VIRTUAL_SERVER_NAME"]
set :repo_url, "#{fetch(:deploy_user)}@#{fetch(:repository_server_name)}:/var/Repositories/Git/#{fetch(:application)}.git"
# Don't forget to make this branch in the repository
set :branch, ENV["VIRTUAL_REPOSITORY_DEPLOYMENT_BRANCH"]
#set :stage, :virtual
fetch(:default_env).merge!(wp_env: :virtual)
For in-depth explanations of the different Capistrano settings have a look at the website and GitHub project page.
Update your other example deployment configurations as you require and commit your changes to the repository.
*You can now remove Capfile
, deploy.rb
and the deploy
folder from ~/Desktop
.
Since we do not version themes and plugins in the project Git repository, what can we do to make sure that the plugins and themes we want get deployed by Capistrano? BedRock uses WordPress Packagist for this. WordPress Packagist is a project that mirrors the WordPress plugins and themes directories to allow them to be packaged using composer.
To use composer
to install themes and plugins, the format is composer require <namespace>/<packagename>
. With WordPress Packagist, use the wpackagist-theme
namespace for themes. For plugins use the wpackagist-plugin
namespace.
For example, to install the twentyfourteen WordPress theme use:
composer require wpackagist-theme/twentyfourteen
To install the wordpress-importer plugin use:
composer require wpackagist-plugin/wordpress-importer
When you are asked to Please provide a version constraint for ...
, use the version given on the theme or plugin page on WordPress.org.
For a fresh BedRock project, I usually require the following themes:
and the following plugins:
Since using Composer to require components updates composer.json
(and subsequently composer.lock
), Capistrano will automatically install the themes and plugins with WordPress each time the project is deployed.
If you are using a custom theme, you may want to set up a Capistrano recipe to fetch it during deployment. Automation is what Capistrano was built for so it should not be too difficult to make it work in your unique setting.
With our WordPress Stack built, we can now deploy the WordPress site. I’ll describe in general how I go about it. If you need specifics, let me know your setup and I may be able to help.
First, I install a development version of the WordPress project on my local machine. Capistrano is not necessary at this point. I use Nginx as the web server and create example configurations in the _meta/
folder. I do the same for other configurations that will be useful for other developers on the project to be aware of.
Secondly, I deploy in three stages:
This article ended up being much longer than I had planned. After all this, I hope you are not put off by what appears to be a lot of configuration just to make a simple WordPress website work. I have deliberately over-explained many of the steps here. Additionally, BedRock is still quite a new project and will most assuredly get better. The beauty of it being an Open Source project is that you can suggest improvements yourself.
The investment in setup pays back in bucket-loads. When the setup is complete, you can deploy updates in a single command.
Hope you found this useful and enjoy using BedRock if you are not already.