Everyone who has ever developed a web app has had to deploy it. Back in the day you simply uploaded your files with FTP and everything would be good. Today we have to clone git repositories, restart servers, set permissions, create symlinks to our configuration files, clean out caches and what not.

Doctor, what’s wrong?

In my opinion there are two critical problems with deployments today:

They are slow

They cause downtime

Both topics have been discussed by the likes of Twitter and Github. They have optimized their deployment process to allow for fast and continuous deployments. But, you are probably stuck with a default Capistrano install. As it turns out, with a little work, it’s quite easy to setup the same deployment process as Github and Twitter use.

For Ariejan.net, I’ve managed to get zero-downtime deployments that run in under 10 seconds. Yes, you read that right.
~

Let’s go!

This guide will help you setup your server and Rails 3.1 project for fast, zero-downtime deployments. I’ll be using Nginx+Unicorn to serve the application, git+capistrano for fast deployments.
~

The shopping list

Here’s a list of ingredients you’ll need:

A recent Ubuntu server (I used 11.04 Netty)

Your Rails 3.1 app

A remote git repository that contains your app

Assumptions

I’m making some assumptions about your app:

Ruby 1.9.2

Rails 3.1 app using Postgres named my_site

You want to use RVM and Bundler

Setting up your server

There are a few things you need to setup before diving in. The first bit is run under the root user.

Note: do this for both root and the deployer user to avoid confusion later on.

Because you’ll be running your app in production-mode all the time, add the following line to /etc/environment so you don’t have to repeat it with every Rails related command you use:

RAILS_ENV=production

Postgres

I know not everybody uses Postgres, but I do. I love it and it beats the living crap out of MySQL. If you use MySQL, you’ll know what to do. Here are instructions for setting up Postgres. First create the database and login as the postgres user:

Okay, that’s Nginx for you. You should start it now, although you’ll get a 500 or proxy error now:

/etc/init.d/nginx start

Unicorn

The next part involves setting up Capistrano and unicorn for your project. This is where the real magic will happen.

You’ll be doing cap deploy 99% of the time. This command needs to be fast. To accomplish this I want to utilize the power of git. Instead of having Capistrano juggle around a bunch of release directories, which is painfully slow, I want to use git to switch to the correct version of my app. This means I’ll have just one directory that is updated by git when it needs to be.

Let’s get started by adding some gems to your app. When done run bundle install.

# Gemfile
gem "unicorn"
group :developmentdo
gem "capistrano"end

The next step is adding a configuration file for Unicorn in config/unicorn.rb:

# config/unicorn.rb
# Set environment to development unless something else is specified
env =ENV["RAILS_ENV"]||"development"# See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete
# documentation.
worker_processes 4# listen on both a Unix domain socket and a TCP port,
# we use a shorter backlog for quicker failover when busy
listen "/tmp/my_site.socket", :backlog=>64# Preload our app for more speed
preload_app true# nuke workers after 30 seconds instead of 60 seconds (the default)
timeout 30
pid "/tmp/unicorn.my_site.pid"# Production specific settings
if env =="production"# Help ensure your application will always spawn in the symlinked
# "current" directory that Capistrano sets up.
working_directory "/home/deployer/apps/my_site/current"# feel free to point this anywhere accessible on the filesystem
user 'deployer', 'staff'
shared_path ="/home/deployer/apps/my_site/shared"
stderr_path "#{shared_path}/log/unicorn.stderr.log"
stdout_path "#{shared_path}/log/unicorn.stdout.log"end
before_fork do|server, worker|# the following is highly recomended for Rails + "preload_app true"
# as there's no need for the master process to hold a connection
if defined?(ActiveRecord::Base)
ActiveRecord::Base.connection.disconnect!
end# Before forking, kill the master process that belongs to the .oldbin PID.
# This enables 0 downtime deploys.
old_pid ="/tmp/unicorn.my_site.pid.oldbin"ifFile.exists?(old_pid) && server.pid != old_pid
beginProcess.kill("QUIT", File.read(old_pid).to_i)
rescueErrno::ENOENT, Errno::ESRCH# someone else did our job for us
endendend
after_fork do|server, worker|# the following is *required* for Rails + "preload_app true",
if defined?(ActiveRecord::Base)
ActiveRecord::Base.establish_connection
end# if preload_app is true, then you may also want to check and
# restart any other shared sockets/descriptors such as Memcached,
# and Redis. TokyoCabinet file handles are safe to reuse
# between any number of forked children (assuming your kernel
# correctly implements pread()/pwrite() system calls)
end

Okay, as you can see there’s some nice stuff in there to accomplish zero-downtime restarts. Let me tell you a bit more about that.

Unicorn starts as a master process and then spawns several workers (we configured four). When you send Unicorn the ‘USR2’ signal it will rename itself to master (old) and create a new master process. The old master will keep running.

Now, when the new master starts and forks a worker it checks the PID files of the new and old Unicorn masters. If those are different, the new master was started correctly. We can now send the old master the QUIT signal, shutting it down gracefully (e.g. let it handle open requests, but not new ones).

All the while, you have restarted your app, without taking it down: zero downtime!

Capistrano

Now for Capistrano, add the following to your Gemfile.

# Gemfile
group :developmentdo
gem "capistrano"end

And generate the necessary Capistrano files.

capify .

Open up config/deploy.rb and replace it with the following.

This deploy script does all the usual, but the special part is where you reset the release paths to the current path, making the whole release directory unnecessary.

Also not that the update_code is overwritten to do a simple git fetch and git reset - this is very fast indeed!

Now there is one little thing you’ll need to do. I like to run my apps, even on the server, to use their own gemset. This keeps everything clean and isolated. Login to the deployer account and create your gemset. Next run rvm info and fill the PATH, GEM_HOME and GEM_PATH variables accordingly.

Don’t forget to install bundler in your new gemset

Database configuration

I always like to keep the database configuration out of git. I’ll place it in the shared directory.