our words.

Someone always has something insightful to say.

Faun

Deploying WordPress with Capistrano and SVN

by Faun

Deploying WordPress with Capistrano and SVN

Recently, Traction deployed a website built on WordPress to a virtual private server, and being the kind of developer I am, I urged the team to try out Capistrano. After toying with it for a while, I realized there was no way I could go back to doing things “by hand.” Capistrano allows you to push files to a server in a very controlled, repeatable way, greatly reducing the margin of error when deploying to a live site. By reducing the workload required to deploy even very complicated web projects, Capistrano allows you to focus on the thing that’s most important—your code.

The initial process of getting Capistrano running was unclear when using WordPress, so I figured I should share what I learned. After playing with it for a while I found it very easy to work with and quite clearly documented behind the scenes. What follows are a few things I’ve learned along the way.

About Capistrano

Capistrano is a script designed to make repeated deployments trivial, and allows you to run arbitrary shell scripts on the server of your choice in a manner not unlike rake. Capistrano tasks are run from the command line. Consider the following:

cap staging deploy

The above will run the task default in the namespace deploy on the server staging. For specific details on how to set this up, see deploy.rb below.

Prerequisites

Command line

A must for any developer (a Unix/Linux server environment assumed in this tutorial). Some basic WordPress-related info on the UNIX shell here. See also: In the Beginning was the Command Line

Subversion

Some sort of version control is a requirement for a team working together simultaneously on the same codebase. WordPress can be checked out of Subversion to ensure an easy upgrade process that allows you to update your local installation of WordPress. See Gabriel’s blog post about setting up WordPress core as a Subversion external.

Ruby

If you’re on a Mac (like us here at Traction), you most likely already have some version of Ruby installed. Otherwise you can download Ruby. If you’re on Windows, I recommend Ruby Installer.

Rubygems

Assuming you’re on a Mac, you already have this, otherwise you can download RubyGems. Simply run this command to update to the latest version:

gem update --system

Capistrano Gem

Originally tool specific to Ruby on Rails, Capistrano has since been modified by the community to include a wide variety of deployment possibilities. It’s worth reading about how the original library works before diving into how to override it to do what we want. A wealth of information about the various Capistrano tasks are available on the project wiki, the handbook and the capitate project, although the latter refers to the Rails-specific version of the library, not the generic overrides.

To install Capistrano, run:

gem install capistrano

Railsless-Deploy Gem

This is the part that makes it possible to use Capistrano with WordPress. By overriding the Rails-specific parts, we gain the ability to deploy many different applications. More info here.

gem install railsless-deploy

More information about the specifics of modifying Capistrano configuration to work with WordPress can be found here.

Capistrano Multistage Gem (optional)

Using the capistrano-ext gem contains a multistage extension that allows you to deploy across multiple servers in as many stages as you want. At Traction, each developer has their own testing environment, and we also have a staging site for each website in production. Install the gem using:

gem install capistrano-ext

Then follow the instructions here to set up the folder structure (or see code below) and put in the necessary requires. Capistrano-ext also comes with quite a few other handy tools that are worth exploring, but are outside the scope of this article.

Initialize Capistrano

To initialize Capistrano, in the root of your project (see local directory structure below, this is the folder that contains your htdocs folder), run the following:

capify .

In order to maintain uploads across releases, we decided to move the uploads directory outside of the htdocs directory. The easiest way to do that is to symlink to the version in a shared location, that is persistent across deploys. Seen below in the directory structure and in the wordpress:symlinks task, the uploads directory symlink is re-created with every deploy, allowing us to maintain uploads across releases. We placed a Subversion ignore on the contents of the shared directories (do a search for svn propset svn:ignore) in order to allow each developer to also have their own uploads. Since the uploads and the database are connected, each installation should have its own database and uploads folder. To allow each installation to use its own database, our db-config.php file is symlinked to the shared directory, so it can remain outside of version control.

Setting Up WordPress Projects with Subversion (optional)

This is worth doing by itself, as WordPress will stay in sync with your local development and updating to the latest release of WordPress becomes a simple matter of running svn update from your working copy. More info here.

After you have completed the steps in that blog post, from the root directory, run:

cd htdocs
mv db-config.php ../shared/db-config.php
ln -nfs ../shared/db-config.php db-config.php

Now take a moment to go edit the db-config.php file in the shared directory with your credentials. Every server to which you will be deploying must have this file configured correctly. Failure to do so will make the deployment fail.

Putting it all Together

Approximate local directory structure:

.
|-- Capfile
|-- config
|   |-- deploy
|   |   |-- production.rb
|   |   `-- staging.rb
|   `--  deploy.rb
|-- htdocs
|   |-- db-config.php -> ../shared/db-config.php
|   |-- wp
|   |   `-- ...
|   |-- wp-config.php
|   `-- wp-content
|       `-- ...
`-- shared
    |-- uploads
    `-- db-config.php

And the associated structure on the server:

/www
|-- production
|   |-- database_backups
|   |-- htdocs -> ../releases/YYMMDDHHMMSS
|   |   `-- db-config.php -> ../shared/db-config.php
|   |-- logs
|   |-- releases
|   `-- shared
|       |-- uploads
|       `-- db-config.php
`-- staging
    |-- export_for_staging.sql
    |-- htdocs -> ../releases/YYMMDDHHMMSS
    |   `-- db-config.php -> ../shared/db-config.php
    |-- logs
    |-- releases
    |   |-- ...
    |   `-- YYMMDDHHMMSS
    `-- shared
        |-- uploads
        `-- db-config.php

Capfile

Our Capfile looks something like this:

load 'deploy' if respond_to?(:namespace) # cap2 differentiator
require 'capistrano'
require 'rubygems'
require 'railsless-deploy'
load    'config/deploy'

Deploy.rb

Our config/deploy.rb looks roughly like the code below. Be sure to fill in the paths and names specific to your application.

set :application, "application name"
set :repository, "http://svn.example.com/path/to/htdocs"

set :stages, %w(staging production)
set :default_stage, "staging"
require 'capistrano/ext/multistage'

set :deploy_via, :copy #we are using copy for deployment because our repository is behind a firewall
set :scm, :subversion
# set :copy_cache, true #this can be used to speed up local checkouts
set :copy_exclude, [".svn", ".DS_Store"]
set :checkout, "export"

set :current_dir, "htdocs" #the directory where you want releases to symlink
set :site_root, "#{deploy_to}/#{current_dir}" #wherever you want files to be served from
#set :use_sudo, false #can be used if you don't have sudo access, like on a shared host
set :keep_releases, 5

before 'deploy:update_code', 'wordpress:symlinks:setup'

after 'deploy:symlink', 'wordpress:symlinks:update'
after "wordpress:symlinks:update", "deploy:cleanup"

namespace :deploy do
    desc <<-DESC
    A macro-task that updates the code and fixes the symlink.
    DESC
    task :default do
        transaction do
            #make sure the release and htdocs directories exists
            run "mkdir -p #{deploy_to}/releases/", :once => true 
            run "rm -rf #{deploy_to}/htdocs/", :once => true
            update_code #update the code from the latest version in the repository
            symlink  #see symlink task below
        end
    end

    task :update_code, :except => { :no_release => true } do
        on_rollback { run "rm -rf #{release_path}; true" }
        strategy.deploy!
    end

    after "symlink" do
        puts "Symlinking #{current_path} to #{site_root}."
        run "ln -nfs #{release_path} #{site_root}"
    end
end

namespace :wordpress do
    namespace :symlinks do
        desc "Setup application symlinks in the public directory"
        task :setup, :roles => [:web] do
                run "mkdir -p #{shared_path}/uploads/"
        end

        desc "Link public directories to shared location."
        task :update, :roles => [:web] do
            #after we have copied the release to the server and symlinked it, we also symlink the uploads directory
            run "rm -rf #{current_path}/wp-content/uploads/"

            #symlink uploads in the current release to the one in shared
            run "ln -nfs #{shared_path}/uploads #{current_path}/wp-content/"

            # replace the db-config.php file with a link to the one in shared
            run "ln -nfs #{shared_path}/db-config.php #{release_path}/db-config.php"
        end
    end
end

Debugging

I wrote a little task while I was getting familiar with Capistrano that helped me better understand how my configuration file was being interpreted. YMMV.

namespace :release_info do
    desc <<-DESC
    Debugging info about releases.
    DESC

    task :default do
        %w{releases_path shared_path current_path release_path releases previous_release current_revision latest_revision previous_revision latest_release}.each do |var|
            puts "#{var}: #{eval(var)}"
        end
    end
end

Enough to get you started.

Hopefully Capistrano will make deploying WordPress sites easier, especially if you’re deploying often. Please let me know in the comments if there is anything that is incorrect or anything that you found particularly useful.

More reading:

Deploying WordPress using Capistrano

Deploying WordPress to SliceHost using Capistrano and Git

Code examples:

ThePixelDeveloper’s recipe on Github

whomwah.com recipe

Cheat

Rails-less Deploy

Capistrano


Clint

Most of the time I have the whole website done, including content, before I deploy to live. Wordpress has all sorts of Hard links (full urls). How do you deal with the database side of the deployment?


Now you say something:

In our effort to prevent spam, we ask that you complete this CAPTCHA before submitting your comment.