Skip to content

Instantly share code, notes, and snippets.

@jexchan
Forked from jrochkind/gist:2161449
Last active April 23, 2024 08:18
Show Gist options
  • Save jexchan/5037517 to your computer and use it in GitHub Desktop.
Save jexchan/5037517 to your computer and use it in GitHub Desktop.
A Capistrano Rails Guide

A Capistrano Rails Guide

by Jonathan Rochkind, http://bibwild.wordpress.com

why cap?

Capistrano automates pushing out a new version of your application to a deployment location.

I've been writing and deploying Rails apps for a while, but I avoided using Capistrano until recently. I've got a pretty simple one-host deployment, and even though everyone said Capistrano was great, every time I tried to get started I just got snowed under not being able to figure out exactly what I wanted to do, and figured I wasn't having that much trouble doing it "manually".

But first bundler, and then especially the Rails asset pipeline, finally pushed me over the edge. There's just a bit too many steps now to be happy doing it manually, and Capistrano does some other useful stuff too.

why another getting started guide?

So I figured out how to do Capistrano. I still found the existing documentation and getting started guides not as good as they could be, for me personally at least. I think this is caused by a combination of two things:

  • Cap is super flexible in terms of what you're pushing out, and how -- that most guides stay too high level and flexible, but these guides leaves people (or at least me) who want to deploy a Rails app, or even another Rack app, in a conventional standard way... with too many choices they don't want to make and things they have to learn.
  • and Cap changing from 1.x to 2.x and changing to keep up with Rails, and when you google, a newbie finds it hard to tell if they're finding something up to date or not.

So here's my attempt to turn what I just learned into a guide for others. I just figured this stuff out, if you know something better, please share in comments!

guide to what

My situation is: Rails, bundler, asset pipeline, apache, passenger, single host deployment (on a standard self-managed linux box, no heroku or what have you), git. Oh, and a bit of rbenv. I'm going to lead you through that, although it may be useful to you if some things differ, and I'll try to point out other paths where appropriate. I'll also try to explain what's going on and tell you where to go for more info, not just give you boilerplate copy-and-paste, where it makes sense.

I wrote the guide I'd have wanted, it might be too verbose for you, but I like to know what's happening.

This is written in March 2012, using capistrano 2.11.12. If you're reading this far in the future, maybe or maybe not this is still good, or maybe cap, rails, bundler, etc, have changed enough that it ain't.

install capistrano

For reasons I can't tell, some people don't add capistrano to their Gemfiles. So you can just:

gem install capistrano

But I prefer actually adding it to my Gemfile, as:

gem "capistrano", :group => :development

and

bundle install

Now, a command has been installed to actually put a skeleton of cap config files in your project. From your project directory:

capify .

Adds a ./Capfile and a ./config/deploy.rb

(Note: there's actually often no reason you'd need to keep the Capfile and deploy.rb with the complete source of your project. It could be somewhere else entirely -- when you deploy, you'll need the Capfile and deploy.rb, but you don't actually normally need the source of your whole project, cap's gonna fetch it from the source control anyway! But most people seem to keep their cap deploy configuration in their main project source -- and 'advanced' use might involve cap tasks that work on the checked out source, to add a git tag or something)

for rails asset precompile

This is actually built into Cap, but not turned on by defualt.

In ./Capfile, uncomment load 'deploy/assets' to get asset precompile behavior.

Now Cap will precompile your assets for you on the production server every time you deploy.

What's it do exactly? Check the source

Why is this in ./Capfile instead of ./config/deploy.rb? Got me, anyone else know? I think it'll work if you put it in deploy.rb instead. Why is it load instead of require? I don't know either.

If you deploy at a SubURI instead of document root

Asset precompilation needs to know this. You can do it with config.asset.prefix, but I consider it a deployment detail that, if it needs to be in source at all should be in cap. You can also use the RAILS_RELATIVE_URL_ROOT when running the precompile rake task.

Here's how to tell cap to do that, adding it on to cap's default precompile env arguments (which is RAILS_GROUPS=assets)

set :asset_env, "#{asset_env} RAILS_RELATIVE_URL_ROOT=/my_sub_uri"

for bundler

An appropriate Cap recipe is built into bundler and already available to you in Cap. In your ./config/deploy.rb, add:

require "bundler/capistrano" 

And Cap will run something like a "bundle --deployment" on the production server every time you deploy.

(What's a bundle --deployment? The bundler docs aren't too helpful, but basically: it insists on installing only exactly the gem versions in your checked in Gemfile.lock, and it installs gems to your local ./vendor instead of system gem location)

What's it do exactly? Well, as you can see from the 'require', it actually lives in the bundler source, hopefully because the bundler team keeps it up to date with any changes to bundler. The source actually isn't too illuminating, until you figure out the actual bundle:install task is defined in bundler source here. Or when you actually do a cap deploy later you can look at the output to see what bundler-related things are happening.

for passenger

At the bottom of the default deploy.rb, there are some lines to uncomment for passenger, that simply have cap touch ./tmp/restart.txt on deploy to trigger a passenger reload. Uncomment em!

# If you are using Passenger mod_rails uncomment this:
# 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

Note that if you tell Cap to install your app to say, /opt/my_app, the actual current deployment will end up in /opt/my_app/current -- so in your apache or nginx config files, where you're telling it where to find the rails/rack app, you'd want /opt/my_app/current/public, etc.

Also note that this provides a basic example of how you can define your own tasks, if you need to. You might not need to for a basic Rails setup, there's enough built-in.

database migrations

Sometimes a new release has some database migrations that need to be run on the production db. (Disclaimer: Some people say never to run migrations on production, but I think I'm not alone in doing so -- it's got some downsides, likely momentary app downtime, but there's no easy better way to do it, without architecting a lot of I don't know what)

Cap's got built in tasks for Rails migrations: You can run cap deploy:migrate to only run migrations on your server (the one marked with role :db and :primary => true) without a new deploy push too, or you can cap deploy:migrations to deploy+migrations. But this requires you to think about "do I need to run migrations this release?"

If you run migrations when they aren't neccesary, then simply nothing will happen. So why not just run them on every deploy? That's what I'm doing. Add to your deploy.rb:

after 'deploy:update_code', 'deploy:migrate'

That's just taking the deploy:migrate task that was already built in, but telling Cap to run it after deploy:update_code, every time.

By default it run migrations against your production db, but you can set, eg:

set :rails_env, "staging"  # will this effect other built-in tasks? Maybe. 

Warning: there's no automated way (I think?) to automatically undo the migrations. You'd have to figure out what Rails migration number you want to go down to, and rake db:migrate down to it yourself if you want to rollback. Unless I'm wrong and it already magically does this; it's theoretically possible, it would be an interesting project for someone to write such a task.

Better? See this idea for running the migrations task only if there are new migrations. (not vetted by your author, I don't entirely understand how it works)

Where do you figure out what hookpoints to hook in 'after' or 'before'? This graphic helps. But I just looked at the source for Cap's built in deploy/assets task, and saw it hooked in after "deploy:update_code", and figured the point to precompile assets was the right point to db migrate too.

What do the cap migration tasks actually do exactly? They're defined, as of this writing anyway, in the recipes/deploy.rb source file.

cleanup old releases

Every time you cap deploy, you get a new copy of your app on your deploy app server(s) in $DEPLOY_DIR/releases/$TIMESTAMP

You probably don't want to keep those around forever eating up more and more disk space. You can run "cap deploy:cleanup" to delete all but the most recent X releases. (looks like at the time I'm writing this default is 5, although some suggest it used to be 3). But why would you want to run that by hand, if you're using capistrano? Have your deploy task automatically clean up old releases after a deploy

Add to your deploy.rb:

set :keep_releases, 5
after "deploy:restart", "deploy:cleanup" 

Newer versions of Cap will include this as a commented out line in your generated ./config/deploy.rb

single server

I only have one server. In ./config.rb, replace all the "role" lines with a single:

server "your.server", :app, :web, :db, :primary => true

Note, I do have my db running on a different server. You might think you need a role :db pointing to that server. However, with built-in capistrano tasks, there's only one thing (as far as I can tell) with role :db -- a server with role :db, if and only if it also has :primary => true is used to run your migrations. If you're like me, you don't actually want cap to try to run the migrations on the db server -- the db server does not allow ssh logins and does not have a ruby/rails stack, it's a db server! The migrations should be run on one of the app servers, in my case the only app server.

This :db thing is confusing. Perhaps advanced cap usage actually wants to do something with your actual db servers, but typical cap only (at least with Rails) uses the :db, :primary => true one for running migrations, which you probably don't actually want to run on an actual db server.

git

Cap always deploys from a source control repository. (You don't want to deploy something to production that isn't even in source control, do you? I didn't think so). It can handle just about any scm there is, but I use git.

set :scm, :git
set :repository, "giturl"

By default, this will deploy whatever is at the head of the 'master' branch in git. Want to deploy from a specific branch, say 'production' or 'deploy'?

set :branch, "deploy"

You can use a specific git tag instead of branch there too, it's still set :branch.

Wanna be able to set the 'branch' on the command line, to mention a specific tag? While still defaulting to 'master' or any other branch you want? In your deploy.rb:

set :branch, fetch(:branch, "master")

Then on your command line:

cap -S branch=branchname deploy

(thanks PizzaPill on stackoverflow )

You also probably usually want to

set :deploy_via, :remote_cache

Otherwise, cap will do a complete brand new git clone with each deploy. This way it reuses a single remote git clone.

auth in git

This git checkout will be happening on your target deploy servers, so will by default be done as whatever user cap is using to login to remote servers, which is a whole different ball of wax (see 'Remote user/permissions' below)

It'll ask you for a password if git does, or do passwordless git checkout if you have it set up, etc. that's fine if the accounts match.

Or you can tell cap what account to use for the git checkout:

set :scm_user, "whatever"

Or if you're me and have a totally brain dead local git server that requires you to hard-code the auth into the git url anyway, then you already did that:

set :repository, "http://USER:[email protected]"

You can probably do something crazy to get USER:PASS from ENV too, out of scope for this guide because I haven't figured it out yet.

You may want to set in deploy.rb

ssh_options[:forward_agent] = true

This way, when cap tries to do a git checkout on the remote server, it will use the ssh keys from your local account (the one you're running cap deploy) on to try a password-less login. Depending on exactly how you're setting things up, this could be useful.

path on target server?

by default, capistrano will put your release on your app server at /u/apps/$app_name (I'm not actually sure where this convention is from). You can specify it yourself, and you can interpolate the "application" setting:

set :application "i_widget_3000"
set :deploy_to "/opt/#{application}"

Remote user/permissions

Okay, there's no getting around a bit of decision making here, and thinking about how you want to set up permissions and such on your deploy servers.

First, understand how Capistrano works: It will execute commands remotely on your deploy server(s) using ssh. To do this, it has to authenticate to your remote server(s) as a certain account. Then it'll, by default anyway, end up creating the release checkout as owned by that account. And under default passenger setup, that means the app will execute as owned by that account.

By default if you do nothing, cap will try to ssh into your remote servers using the same account name you are logged into on the server you're running the cap deploy command. It'll prompt you for a password unless you have password-less ssh login setup. If your account names match, this may work fine -- but I don't want the file ownership of the release to be under my own developer account, and I don't want the rails app to be run by passenger under that account. It's just poor organizational practice, and even in my small shop several developers (or my hypothetical eventual replacement) might do a deploy, and I don't want the ownership depending on who happened to do the deploy.

It's unclear to me what standard best practices here are. But here's what I figured out for myself:

  • On the remote app server(s), I create an account with the same name as my app, say i_widget_3000
  • If my app deploy_to is /opt/i_widget_3000, I create that directory 'out of band', not through cap, and chown it to i_widget_3000 account.
  • the i_widget_3000 account doesn't need sudo rights or any other special permissions, and can be further limited by whatever sysadminy magic you like to be very limited privs. All it needs to do is be able to read, write, and execute in /opt/i_widget_3000 -- just what you want in an account that web software is going to be running as. (This is why we created that directory 'out of band' -- cap's going to log in as i_widget_3000, and won't have permissions to create directories itself under /opt.)
  • I set up ssh password-less login from my developer account on whatever machines I'll be running cap deploy to the i_widget_3000 account on the target deploy server(s).
  • Other developers that need to do deploys, they just need to set up password-less login too, they can run the cap deploy as is, the release ownership will always be i_widget_3000.
set :user, "i_widget_3000" # you could even do `set :user, application` here
# cap assumes you want to do things with sudo on remote servers, we don't and in fact
# intentionally can't, no problem:
set :use_sudo, false 

There are certainly other ways to set things up. You could set up a capistrano account that cap will use to login, but then tell cap to chown the files to the actual account that passenger will run the app as. Then capistrano acct needs to have privileges to do the chown somehow, but the actual app account can still be limited priv. You could have cap actually log in as the individual developer account (default), but then chown. You could even have some fancy thing where cap reads what account to login as from the current ENV, so different developers log in as their own account, but the account names don't have to match on different machines.

But this way seemed the simplest way to do what I needed.

passwordless ssh login

There are other guides on the net to this, but basically on the machine you'll be running cap deploy on, you want to generate your key pair if you haven't already:

ssh-keygen -t rsa

That'll create a ~/.ssh/rsa_id and ~/.ssh/rsa_id.pub

You want to take the contents of the the rsa_id.pub and add them to the end of a file on the target machine, under the account you'll be pushing to, ~/.ssh/authorized_keys -- note it's important to copy exactly, not introducing any new newlines etc. You can use the built-in openssl utility to do so, if password login is currently enabled for the target account and you know the password.

ssh-copy-id -i ~/.ssh/id_rsa.pub remote_username@remote_host

Okay, you're all configured, time to deploy

if you've never deployed to the server(s) before, on the command line:

cap deploy:setup

Then, for the first and subsequent times you want to push a new deploy release:

cap deploy

Note, why have to think about if you need to deploy:setup or you've already done it? Why not just make deploy pre-emptively run a setup every time? The default deploy:setup is cheap and idempotent (it just makes some directories), so I don't know why not or why nobody does this. I think you could:

before "deploy", "deploy:setup"

Some other useful tasks built into cap

cap -T on the command line to see list of all available cap tasks.

cap -e taskname to see some help/description for a specific task.

cap deploy:rollback can undo a deploy, and re-deploy the next-to-latest deployed release. Run it again to go back yet one more version. If you run out of versions, it will tell you and refuse to do it. Useful when you deploy and immediately realize it was a mistake. (Note: I don't think rollback will take care of going down migrations, if the latest release had migrations. So if you auto-migrate on every deploy, beware).

You can use cap deploy:web:disable to temporarily replace your app with a static maintenance message, and cap deploy:web:enable to remove it, IF you put something custom in your apache or nginx conf (to redirect all access to maintenance page if it exists). Run deploy:web:disable, it'll tell you what. Here's a suggestion on how to supply a custom maintenance page instead -- would have to be customized at least slightly for cap 2.x, I haven't tried it yet. (these tasks have been removed from capistrano, although you can still create them yourself locally.

Debugging!

You can run cap with a "-d" flag, and it'll go through all the server commands it will exec one by one, and ask you whether to run each one.

For instance cap deploy -d.

This can be very useful for debugging deployments that are failing; you can have cap go until the point it would fail, and then go investigate the server to figure out why.

Bonus Feature: rbenv

I use rbenv on my deploy server(s), via a somewhat experimental rbenv system-wide install

We want to make sure cap can find the 'right ruby' using rbenv when it logs into the remote deploy servers. No problem!

set :default_environment, {
  'RBENV_ROOT' => '/usr/local/rbenv',
  'PATH' => "/usr/local/rbenv/shims:/usr/local/rbenv/bin:$PATH"
}

We also want to have cap's bundler install use binstubs, that are set to use rbenv too, no problem!

set :bundle_flags, "--deployment --quiet --binstubs --shebang ruby-local-exec"

You could adapt this technique for some other kind of production rbenv install too. rbenv in production doens't work completely braindead smoothly yet.

cap will end up executing under whatever the rbenv 'default' ruby is on the deploy machine(s). You want to tell it to use a specific ruby (and make the binstubs for that ruby too), no problem, just add the appropriate ENV variable for rbenv:

set :default_environment, {
  'RBENV_VERSION' => '1.9.3-p0',
   #...
}

I much prefer that to the level of indirection of pushing a project-specific .rbenv file with your project, this seems more transparent and simpler.

(thanks to here and here )

More Bonus: Other useful cap add-ons

License

This guide licensed CC-BY. Actually, you don't even need to give me attribution, but CC curiously doesn't offer a license like that anymore? Do what you will with it, if you wanna turn it into some official cap docs on the cap wiki, go for it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment