Multi-stage deployment with capistrano
I want to talk a bit about capistrano. It took me a while to get on the boat, well here I am, and I am very happy with it. I am going to show an example of a simple multi-stage deployment setup, developing on a mac and then deploy to staging and production. I will also show how to clone your production data for debugging purposes.
I am assuming that your staging environment and production environment is located at the same server. I am also assuming that you have a private public key pair, and that the public key has been transferred to the aforementioned server.
Prerequisites
Install Capistrano and multistage extension:
gem install capistrano
If you are using Bundler, put this is your Gemfile:
group :development do
gem "capistrano"
gem "capistrano_transmit"
end
Capify your project by issuing this command in the root folder of your project
capify .
Your project is now capified!
Config files
When capifying your project, Capistrano creates a file in your config directory, called deploy.rb. This is the main configuration file. We also need to crete a subfolder called deploy/, with two files in it, staging.rb and production.rb. You should end up with this structure:
...
|
|- config
|
...
|
|- deploy.rb
|
|- deploy
| |
| |- production.rb
| |
| |- staging.rb
...
Edit deploy.rb
open the file deploy.rb in your text editor:
require 'capistrano/ext/multistage'
require "bundler/capistrano"
require 'capistrano/transmit'
set :application, "name-of-application"
set :scm, :git
set :repository, "https//github.com/username/repository.git"
# use private/public keys for repo connection, otherwise set username and password here and uncomment
# set :scm_username, ""
# set :scm_passphrase, ""
set :user, "deploy"
set :deploy_via, :remote_cache
set :ssh_options, { :forward_agent => true }
set :stages, ["staging", "production"]
set :default_stage, "staging"
set :use_sudo, false
set :default_shell, "bash --login"
after "deploy:restart", "deploy:cleanup"
# task to backup the production database before each deploy to production
task :backup, :roles => :db, :only => { :primary => true } do
filename = "#{deploy_to}/backups/#{application}-#{rails_env}.dump.#{Time.now.to_f}.sql.bz2"
text = capture "cat #{release_path}/config/database.yml"
yaml = YAML::load(text)
on_rollback { run "rm #{filename}" }
run "mysqldump -u#{yaml[rails_env]['username']} -p#{yaml[rails_env]['password']} #{yaml[rails_env]['database']} | bzip2 -c > #{filename}"
end
# deployment tasks
namespace :deploy do
task :start do ; end
task :stop do ; end
# restart the server
task :restart, :roles => :app, :except => { :no_release => true } do
run "#{try_sudo} touch #{File.join(current_path,'tmp','restart.txt')}"
end
# clone the production database to your environment
task :clone_production_database, :except => { :no_release => true } do
text = capture "cat #{release_path}/config/database.yml"
yaml = YAML::load(text)
run "mysql -u#{yaml[rails_env]['username']} -p#{yaml[rails_env]['password']} --execute='CREATE DATABASE IF NOT EXISTS #{yaml[rails_env]['database']}';"
run "mysqldump -u#{yaml['production']['username']} -p#{yaml['production']['password']} #{yaml['production']['database']} | mysql -u#{yaml[rails_env]['username']} -p#{yaml[rails_env]['password']} #{yaml[rails_env]['database']}"
end
end
after "deploy:update_code", "deploy:migrate"
open the file /config/deploy/production.rb and put this in there:
server "servername-of-production", :app, :web, :db, :primary => true
set :deploy_to, "/path/to/production/on/server"
set :rails_env, "production"
before "deploy:migrate", "backup"
open the file /config/deploy/staging.rb and put this content in there:
server "servername-of-staging", :app, :web, :db, :primary => true
set :deploy_to, "/path/to/staging/on/server"
set :rails_env, "staging"
set :keep_releases, 1
before "deploy:migrate", "deploy:clone_production_database"
after "deploy:update_code", "deploy:copy_assets"
Remember to replace all the dummy values above with your own values.
Okay - that was a lot of code. What’s happening here. The scripts you just made configure capistrano for a couple of things:
- We are deploying using a git repository
- There are two stages: staging and production. You preview your code on staging and if it’s working you deploy to production.
- The default deploy is to staging
- No releases are kept on staging
- When deploying to staging, first the production database is cloned to staging. Then the assets are copied from production to staging (with capistrano-transmit). So you are previewing on a fresh copy of production.
- When deploying to production, the database is backed up. It is backed up to a subdir of the deploy dir called backups. The file name is the a mixture of the application name, environment and date. It’s a bzipped sql file.
Good luck using this script.