You just made an awesome site, the code is in the repo and its all ready to be put on the net. Not a sysadmin? No problem! I will walk you through a very basic Capistrano setup for Rails 3. For some context we’ll be using the current setup of my blog.
Please note that this article assumes you have sudo permissions to install various things on your system like gems and modules.
System stack
First off let’s look at the basic system stack. From top to bottom we:
- Ubuntu 10.04 LTS
- Apache 2
- Passenger 3
- RVM (ruby-1.9.2-head)
- Rails 3
- Capistrano
- Bundler
For source control management I am using mercurial . If your still using svn or cvs or another centralized repository you should really look into a distributed one such as mercurial or git. I admit svn has way better support for large binary files… but those probably shouldn’t be in your repo anyway.
Let’s move on.
Capifying your project
Capistrano is a really easy to use deployment tool. What it basically does is act as rake/make script for running system commands on a remote server. Capifying your project is pretty easy once you get the hang of it. It requires some minor setup but once you have some decent cap scripts you should be able to copy them from one project to another. You can find some documentation about Capistrano (a bit incomplete) here
Let’s begin by capifying your project. Start by installing the gem on your dev box.
gem install capistrano
Next change directory to the project you wish to capify. Then run:
capify .
Two files, Capfile and config/deploy.rb, should have been created. Let’s take a look at my Capfile.
# General stuff
set :use_sudo, false # Your deployment user should never have sudo access
set :user, "mastercontrol" # The user who will run your apps.
# Set application stuff
set :application, "www.matthewbasset.com" # Your ruby on rails application
role :web, "www.matthewbasset.com" # Your HTTP server, Apache/etc
role :app, "www.matthewbasset.com" # This may be the same as your `Web` server, it may not. Tells capsitrano where your app is hosted.
role :db, "www.matthewbasset.com", :primary => true # This is where Rails migrations will be run.
set :deploy_to, "/var/www/#{application}" # Where your app code will be deployed to on the production server.
# Set version control stuff
set :scm, :mercurial # Options: `accurev`, `bzr`, `cvs`, `darcs`, `git`, `mercurial`, `perforce`, `subversion` or `none`
set :repository, "/home/hg/repos/www.matthewbasset.com" # The location of your repository
set :scm_username, "hg" # The source control user. This should not be the same person running the app.
# RVM Setup
$:.unshift(File.expand_path('./lib', ENV['rvm_path'])) # Add RVM's lib directory to the load path.
require "rvm/capistrano" # Load RVM's capistrano plugin.
set :rvm_ruby_string, 'ruby-1.9.2-head' # Or whatever env you want it to run in.
set :rvm_type, :user # The rvm user should probably be the user running the app.
Your file should be substantially smaller and probably won’t include anything related to RVM. If your not using RVM then you don’t need to add that stuff to yours.
If you are using rvm however the two lines you should be concerned with are these:
set :rvm_ruby_string, 'ruby-1.9.2-head' # Or whatever env you want it to run in.
set :rvm_type, :user # The rvm user should probably be the user running the app.
I am going to assume you are using a single instance of RVM under the same user who will be running the webapp :user. Also you need to specify the version of ruby (in my case 1.9.2-head) that rvm should run your app under.
Also notice how I have set me “use_sudo” to “false”. The user who will eventually run your web-app should not be on the sudoers list. This means they should never be able to become root. Additionally you should probably give this user a super strong password, and then immediately forget it. You will never need to run sudo as this user.
Adding some extra cap tasks
You should notice your config/deploy.rb file had some lines commented out. Those are used when deploying with passenger. Since (for the purposes of this tutorial) we are deploying to passenger un-comment these. I would also suggest moving them to a new *.rb file in one of the folders where Capistrano searches for recipes.
With that in mind we have the following:
# Overriding default cap deployment tasks for passenger
task :start do ; end
task :stop do ; end
namespace :deploy do
task :restart, :roles => :app, :except => { :no_release => true } do
run "#{try_sudo} touch #{File.join(current_path,'tmp','restart.txt')}"
end
end
We are also going to need to add some additional commands for bundler.
# Run bundler after code updates
require 'bundler/deployment'
namespace :bundler do
task :create_symlink, :roles => :app do
shared_dir = File.join(shared_path, 'bundle')
release_dir = File.join(release_path, '.bundle')
run("mkdir -p #{shared_dir} && ln -s #{shared_dir} #{release_dir}")
end
task :install, :roles => :app do
run "cd #{release_path} && bundle install"
on_rollback do
if previous_release
run "cd #{previous_release} && bundle install"
else
logger.important "no previous release to rollback to, rollback of bundler:install skipped"
end
end
end
task :bundle_new_release, :roles => :db do
bundler.create_symlink
bundler.install
end
end
after "deploy:rollback:revision", "bundler:install"
after "deploy:finalize_update", "bundler:bundle_new_release"
Notice how this adds symlinks for your bundle so that you don’t need to create a new bundle on each install. It’s also nice to have the rollback option in-case a deployment fails.
Finally lets add some extra tasks for debugging migrating and cleanup.
# Cleanup and debug stuff
after "deploy:restart", "deploy:cleanup"
task(:debug_env) { run "env" }
# Migration stuff
after "deploy:finalize_update", "db:symlink"
after "deploy:finalize_update", "deploy:migrate"
namespace :db do
desc "Updates the symlink for database.yml file to the just deployed release."
task :symlink, :except => { :no_release => true } do
run "ln -nfs #{shared_path}/config/database.yml #{release_path}/config/database.yml"
end
end
This adds:
- Automatic cleanup after successful deployment (after at least 5 releases)
- Auto-linking of your database.yml file to the current release
- Auto-migration of your db on successful code update.
After everything is said and done you should have a file that looks something like:
# Overriding default cap deployment tasks for passenger
task :start do ; end
task :stop do ; end
namespace :deploy do
task :restart, :roles => :app, :except => { :no_release => true } do
run "#{try_sudo} touch #{File.join(current_path,'tmp','restart.txt')}"
end
end
# Run bundler after code updates
require 'bundler/deployment'
namespace :bundler do
task :create_symlink, :roles => :app do
shared_dir = File.join(shared_path, 'bundle')
release_dir = File.join(release_path, '.bundle')
run("mkdir -p #{shared_dir} && ln -s #{shared_dir} #{release_dir}")
end
task :install, :roles => :app do
run "cd #{release_path} && bundle install"
on_rollback do
if previous_release
run "cd #{previous_release} && bundle install"
else
logger.important "no previous release to rollback to, rollback of bundler:install skipped"
end
end
end
task :bundle_new_release, :roles => :db do
bundler.create_symlink
bundler.install
end
end
after "deploy:rollback:revision", "bundler:install"
after "deploy:finalize_update", "bundler:bundle_new_release"
# Cleanup and debug stuff
after "deploy:restart", "deploy:cleanup"
task(:debug_env) { run "env" }
# Migration stuff
after "deploy:finalize_update", "db:symlink"
after "deploy:finalize_update", "deploy:migrate"
namespace :db do
desc "Updates the symlink for database.yml file to the just deployed release."
task :symlink, :except => { :no_release => true } do
run "ln -nfs #{shared_path}/config/database.yml #{release_path}/config/database.yml"
end
end
To verify all your commands are being detected by Capistrano issue the following command:
cap -vT
and assuming you added all custom tasks above issue the following to see if Capistrano can make a connection and runs in the correct environment on the server.
cap debug_env
If everything runs correctly you should be good to go for setup.
Setting up your app for the first time.
Your using a public key for login right. If not your going to need to type the password of the user your deploying as each time you issue a command. Issue the following command:
cap deploy:setup
This will setup the directory structure for your webapp in the :deploy_to folder. For security and consistency a default database.yml file should never be stored in your repository. For that same reason Capistrano does not automatically create this file on setup. Therefore the next step will be to create a database.yml so the symlink task will work correctly. To do this:
- Log into your server
- Navigate to the “shared” folder for your webapp located in your :deploy_to folder
- Create a config folder
- Create a database.yml file
Next issue the command:
cap deploy:update_code
With the current set of cap scripts your going to need to run your first bundle and db creation manually. I may update the scripts later to do this automatically but for now its only once at the initial setup stage. Navigate to your app and run the following:
bundle install
rake db:create RAILS_ENV=production
Finally with everything setup issue the following command.
cap deploy
Hopefully everything goes smoothly and you’ll see a ton of stuff fly by without errors, your database will be updated and your code deployed. If not read the errors Capistrano gives and fix as necessary.
Credits go out to:
- https://github.com/capistrano/capistrano/wiki/Documentation-v2.x
- http://rvm.beginrescueend.com/integration/capistrano/
- http://www.mattvsworld.com/blog/2010/03/rails-3-bundler-capistrano/