Deploying Zend Framework apps with Capistrano

Posted

One of the good things the Ruby community has brought us is Capistrano, a command line tool for automated deployment. Although it was written for Rails apps, it can be used with other languages, as proven by a blog post from a few years ago detailing how to use it to deploy PHP code. I thought I'd update this to show how it can be used to deploy Zend Framework applications.

If you're not familiar with Capistrano, once it's all setup you'll be able to run:

cap production deploy

from your ZF project folder. This will automatically login to your remote server, checkout a copy of your code, run any custom tasks (e.g. setting permissions), and then switch the live site to point at the new release. If anything goes wrong with any of these steps, the deployment will stop, the new checkout will be removed, and your site will remain exactly as it is. This allows you to very easily deploy code updates and new versions with basically no downtime.

I'm going to assume you already have Capistrano installed (if not, there are plenty of guides around for this). You'll also need the capistrano-ext gem for the multistage stuff I'm going to mention later. For reference, at the time of writing I have versions capistrano 2.5.5 and capistrano-ext 1.2.1 of these gems.

Capistrano has a command capify which creates the files it requires, however this doesn't work with ZF applications because the file structure is slightly different. So we're going to create these files manually. If you're using the recommended ZF application structure (i.e. you have an application/configs folder), I've packaged up my examples below into an archive you can just extract into the root folder of your ZF project, but read on anyway so you know what the files do.

The first of these is Capfile, which needs to live in the root directory of your ZF project. This should look something like this:

load 'deploy' if respond_to?(:namespace) # cap2 differentiator
load 'application/configs/deploy'

this file simply loads in your main deployment configuration which in the above example will be application/configs/deploy.rb. If you aren't using the standard ZF app structure, adjust this path to point at wherever you want your deployment configuration to live.

The next file is deploy.rb, which is where most of the deployment settings live and is the file referenced above. Create this file in your application/configs folder. Here's an example:

# general settings
default_run_options[:pty] = true
set :use_sudo, false

# source control settings
set :scm, :git
set :deploy_via, :remote_cache
set :repository, "ssh://git@example.com/yourapp.git"

# stages
set :stages, %w(staging production)
set :stage_dir, "application/configs/deploy"
require 'capistrano/ext/multistage' 


namespace :deploy do

  task :migrate do
    # overrides the standard Rails database migrations task
  end

  task :start, :roles => :app do

  end

  task :stop, :roles => :app do

  end

  task :restart, :roles => :app do
    # no restart required for Apache/mod_php
  end 

end

Adjust the source control section to contain details for the repository your application lives in. The example above shows typical settings for a git repo (replace git@example.com/yourapp.git with your clone URL). If you're using Subversion, try something like this:

set :scm, :subversion
set :repository, "svn+ssh://example.com/yourapp/trunk"
set :scm_username, "your_svn_username"
set :scm_password, "your_svn_password"
set :deploy_via, :export

The stages section allows you to define a number of different environments for your application. In my example I'm defining two stages - production and staging. You then need a file for each which contains the appropriate login details. :stages_dir defines where these files live, in my example it's application/configs/deploy/. These files need to be named the same as your stages (with a .rb extension), e.g. production.rb. Here's an example:

role :app, "example.com"
role :web, "example.com"
role :db, "example.com", :primary => true

set :deploy_to, "/path/to/your/app/"

set :user, "your_ssh_user"
set :password, "your_ssh_password"

Set :app to be the hostname of your remote server. :web and :db will usually be the same, unless your have separate dedicated servers for each of these and need to run any custom tasks on them later. :deploy_to should be the full path to the location on your remote server where you want your application to live. Change :user and :password to be the SSH login details for your remote server.

To add a new stage, simply add it to the :stages variable in deploy.rb (%w(staging production) is the Ruby equivalent of array('staging', 'production')), and then create a file in application/configs/deploy/ with the appropriate login details.

The deploy.rb example above also contains a section that overrides the default Capistrano tasks :migrate, :start, :stop and :restart. These are pretty Rails-specific, so we override them because we don't need them. However if you have any custom restart requirements you'd define them here. E.g. if you're using APC with stat set to 0, you might want to add some code to the :restart task to restart Apache (this will run after each deployment).

Assuming you've got all that setup correctly, it's time to give it a try. (Remember these commands will be performing actions on your remote server!) First run:

cap production deploy:setup

this will create default Capistrano folders on your remote server. After running it, login to your remote server and in your :deploy_to location you should see two folders: releases, and shared. Then (from your local machine again), run:

cap production deploy:cold

this will run the first full deployment of your application. If you check your remote server again, you'll find a new timestamped folder in the releases folder that contains a full copy of your code. You'll also see a 'current' symlink at the :deploy_to location that symlinks to the latest release. So just set your vhost to point at (your path)current/public and your site should be up and running!

Then in future, to deploy a new update you just have to run:

cap production deploy

this will checkout a new copy of the code, and assuming no problems, switch the 'current' symlink to point at the new release. And that's it! To perform the same thing on your staging setup (or any other stages you add), simple replace 'production' with the name of the stage in the commands above. E.g. cap staging deploy.

Hopefully that should be enough to get you started. Run cap -T from your ZF project folder to see what other commands you can use, and the Capistrano FAQ is a good place to start if you need to customise anything.

Comments

marcio
14th Mar, 2011

Very nice article. Really. :) Thanks a lot. I have a question. In order to allow this on a shared host environment what should we have into consideration? Thanks a lot.

marcio
2nd Apr, 2011

The first question was to extensive. Perhaps this one: role :db, "example.com", :primary => true - we are avoinding the migration task ok. On a Zend context and, let's suppose, a mysql case, how can we make use of this task :db ? Thanks a lot.

Marcio
2nd Apr, 2011

And... one last one: Why do we need the namespace ? What does it does here? Thanks a lot, again.

Tim Fountain
11th Apr, 2011

Hi Marcio,

This should all be fine on a shared host as long as you have SSH access. On some hosts there are some issues if you don't have access to a directory outside of the web root, it all depends a little on how your host is setup.

Database migrations is a tricky thing - there's nothing built into ZF to do this at the moment but there are a few home grown solutions if you have a hunt around.

The namespace :deploy is just there because Capistrano can be used for different things, so this just groups together the deployment related commands.

Marcio
19th Apr, 2011

Thank you. :) The shared host should have ssh, we should have ssh keys configured, and, we can enable git (if allowed) and use the remote_cache option. I've created a deploy.rb a little differently but this article was a great source point. Thanks again for this. ;) Cheers.

Add Comment

(Never shown on the site)

(Newlines preserved, format with Markdown)