HOWTO setup multistage deployment with Capistrano

These instructions are outdated. They were written for Capistrano 2.x. As of March 2016, Capistrano 3.x is current.

When I wrote about deploying PHP sites with Capistrano, step 6 was a bit of a throwaway — hey, here's a quick hack for handling multistage deployment. I'd been using variants of that technique for quite some time but figured there must be a better way. There is: Capistrano multistage. There is even documentation for this. But it misses a few steps.

How to setup multistage deployment with Capistrano

  1. Install capistrano and capistrano multistage
  2. Capify your project
  3. Set up the deploy/ directory
  4. Create the deployment recipes

In my Rails-based example, we'll have two stages: staging and production. You can name your stages whatever you want. However, don’t name one of your stages stage — that's a reserved word. Call it staging.

Install capistrano and capistrano multistage

gem install capistrano
gem install capistrano-ext

Capify your project

capify .

Set up the deploy directory

mkdir config/deploy
touch config/deploy/staging.rb
touch config/deploy/production.rb

Create the deployment recipes

You’re going to edit three files: deploy.rb, staging.rb and production.rb:

deploy.rb

set :stages, %w(staging production) set :default_stage, "production" require 'capistrano/ext/multistage'
default_run_options[:pty] = true
set :application, "myproject" set :use_sudo, false set :keep_releases, 5
set :repository, "https://somewhere/svn/myproject/trunk" set :scm, :subversion set :scm_username, "me" set :deploy_via, :export
# this is useful in a shared hosting environment, where you have your own JAVA_HOME or GEM_HOME. # otherwise, just set RAILS_ENV set(:rake) { "JAVA_HOME=#{java_home} GEM_HOME=#{gem_home} RAILS_ENV=#{rails_env} /usr/bin/env rake" }
# since :domain is defined in another file (staging.rb and production.rb), # we need to delay its assignment until they're loaded set(:domain) { "#{domain}" } role(:web) { domain } role(:app) { domain } role(:db, :primary => true) { domain }
namespace :deploy do task :start do ; end task :stop do ; end task :restart, :roles => :app, :except => { :no_release => true } do run "#{try_sudo} touch #{File.join(current_path,'tmp','restart.txt')}" end end
# for some reason, this isn't enabled by default after "deploy:update", "deploy:cleanup"
# here is an example task which uses rake, as defined above after "deploy:migrate", "load_sample_fixtures" desc "load sample fixtures" task :load_sample_fixtures do run "cd #{current_release}; FIXTURES=samples #{rake} db:fixtures:load" end

staging.rb

set :deploy_to, "/path/to/#{application}-stage" # My Rails app uses RJB, so it needs to know where Java lives set :java_home, "/path/to/java-6-openjdk" set :domain, "testing.myserver.ca" set :user, "paul" set :rails_env, "staging" # I am root on my staging server and have all the right gems installed # so I don't need GEM_HOME to be overridden set :gem_home, nil

production.rb

set :deploy_to, "/home/myuser/#{application}" set :java_home, "/home/myuser/sw/jdk" set :domain, "www.myserver.ca" set :user, "myuser" set :rails_env, "production" set :gem_home, "/home/myuser/ruby/gems/"
# in a shared hosting environment, you often need to specify your own passenger configuration desc "copy the .htaccess file (passenger configuration); setup_load_paths.rb (sets GEM_HOME)" namespace :deploy do task :copy_htaccess do run "cp #{current_release}/config/htaccess_production #{current_release}/public/.htaccess" run "mv #{current_release}/config/production_setup_load_paths.rb #{current_release}/config/setup_load_paths.rb" end end
after "deploy:update_code", "deploy:copy_htaccess"

Now, you’re set. You can deploy with cap deploy (for the default stage, production), or cap staging deploy. TextMate's Capistrano plugin handles this gracefully, presenting you with a dialog listing the available stages.

Join the Conversation

11 Comments

  1. Hi Paul,
    Thanks for the post, really useful. It would have been perfect for my if I didn’t spend an hour figuring out why it was running the staging server in production env. I found somewhere else that the apache config needed “RailsEnv staging” within the VirtualHost.
    Thought it might be useful to others.
    Thanks again!

  2. @Marc You can alternatively put “RailsEnv staging” in public/.htaccess that is likely what the copy_htaccess task is for :)

  3. I seem to be having issues with the “:primary => true” part of the :db role. My deploy.rb file contains the following…
    (destination is set in the miltistage files)


    set (:domain) {"#{destination}.#{application}.com"}
    role (:app) {domain}
    role (:web) {domain}
    role (:db, :primary => true) {domain}

    And I’m getting the following error…


    /Users/sjf/.rvm/gems/ruby-1.9.3-p194/gems/capistrano-2.13.4/lib/capistrano/configuration/loading.rb:93:in `instance_eval': ./config/deploy.rb:25: syntax error, unexpected ',', expecting ')' (SyntaxError)
    role (:db, :primary => true) {domain}
    ^

  4. ruby 1.9.3p194 (2012-04-20 revision 35410) [x86_64-darwin11.4.0]

    Turns out this works…

    role :db, :primary => true do
    domain
    end

    It doesn’t matter if I use “:primary => true” or “primary: true” it behaves the same. However, as soon as I put the parens around “:db, :primary => true” or replace do/end with {} the syntax fails.

  5. Hi,
    After execute ‘cap staging deploy’ I am getting the following error:
    capistrano/ext/multistage requires Capistrano 2

    why it happening? please help me regarding this.

  6. These instructions are for Capistrano 2. You have Capistrano 3. You need newer instructions. I’ve updated the article to indicate that it is now outdated.

Leave a comment

Your email address will not be published. Required fields are marked *