Deploy Rails with Capistrano 3

Tweet

CapistranoLogo

Capistrano is a remote server automation and deployment tool written in Ruby. It is an awesome tool which extends the rake (ruby make) DSL and can be used to deploy any web application.

In this article, I will be deploying a Rails application to an EC2 Ubuntu instance. You can follow along to deploy your application to any other PaaS platform, such as Digital Ocean, with ssh access.

I am using an Ubuntu instance for deployment and I’ll assume you already have a working application to be deployed remotely.

Step 1: Server Setup

First you need to create an EC2 instance from the Amazon AWS console, ssh into the instance, and install the necessary packages (database, webserver, etc.) to run your web application. You will have to update the firewall settings on EC2 and on Google App Engine, otherwise your application won’t be accessible from outside world. Another thing is to point your domain to the instance you have created using the DNS tools provided by your domain registrar

I am not going to provide step by step instructions to setup your server. You may use this server setup script to setup your server for deploying a Rails application.

$ ssh -i <path-to-pem-file> ubuntu@<ec2-instance-address>
$ wget "https://gist.githubusercontent.com/42races/8d2c597d10dc9a0bddb0/raw/cbc4373b27dc09d2fc88a660ef1ad1b4c246c0e4/server_setup.sh"
$ chmod +x server_setup.sh
$ ./server_setup.sh

This script will install the necessary packages, creates a deploy user who manages our Rails application, downloads and installs RVM and Ruby, and add your ssh public key to the instance’s authorized keys.

I am using nginx for this application with the following configuration. Put your configuration in a file (eg: foobar.conf) and copy it to the /etc/nginx/sites-available directory. I am going to use “foobar” as my application name and “foobar.com” as my application domain address.

upstream foobar {
  # uncomment the following line if multiple application servers are used.
  # this will force nginx to send requests from the same client to the same
  # application server.
  # ip_hash;
  server unix:///home/deploy/apps/foobar/shared/tmp/sockets/puma.sock fail_timeout=0;
}

server {
  listen 80 default deferred;
  server_name foobar.com;
  root /home/deploy/apps/foobar/current/public;

  location ^~ /assets/ {
    # gzip_static on;
    expires max;
    add_header Cache-Control public;
  }

  try_files $uri/index.html $uri @foobar;
  location @foobar {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://foobar;
  }

  error_page 500 502 503 504 /500.html;
  client_max_body_size 4G;
  keepalive_timeout 10;
}

Step 2: Install Capistrano

Go to your Rails application and add the following line in the Gemfile

group :developemnt do
  gem 'capistrano', '~> 3.1.0'
end

Run the bundle command in the application directory to intall the gem.

$ bundle

check the version of the installed capistrano as follows

$ bundle exec cap -V

Capistrano Version: 3.1.0 (Rake Version: 10.2.2) ###

Capistrano comes with a set of default tasks that you can list using the following command.

$ bundle exec cap -vT

Step 3: Initialize Capistrano

Run the following command to initialize capistrano.

$ bundle exec cap install

The above command creates few files in your application directory.

Capfile
config/deploy.rb
config/deploy/production.rb
config/deploy/staging.rb
lib/capistrano/tasks          # directory

As you can see, it generated a different configuration file for production and staging environments. Since version 3, capistrano is multistaged by default. There is a global configuration file called config/deploy.rb where you can put the configuration commnon to all the environments.

Step 4: Edit Capfile

Open the Capfile in your favourite editor and add/uncomment the following lines

require 'capistrano/setup'
require 'capistrano/deploy'

Capistrano has been re-architectured to be use plugins. In our case, we can use the bundler, rvm, and rails plugins. Plugins are packaged as gems, so we can add them to the Gemfile and require it in the Capfile. Here is the list of official plugins.

We need an application server to run our application, and I am going to use puma.

Add this to the Gemfile:

group :development do
  gem 'capistrano', '~> 3.1.0'
  # cap tasks to manage puma application server
  gem 'capistrano-puma', require: false
  gem 'capistrano-rails',   '~> 1.1', require: false
  gem 'capistrano-bundler', '~> 1.1', require: false
  gem 'capistrano-rvm',   '~> 0.1', require: false
end

And bundle to install these gems. In the Capfile, add the following lines:

require 'capistrano/rvm'
require 'capistrano/bundler'
require 'capistrano/puma'
require 'capistrano/rails/assets' # for asset handling add
require 'capistrano/rails/migrations' # for running migrations

Step 5: Global Configuration

Now open config/deploy.rb in your favourite editor to set various options, like application name:

set :application, 'foobar'                       # application name
set :repo_url, 'git@example.com:me/foobar.git'   # your repo url
set :deploy_to, '/home/deploy/apps/foobar'

Set the version control you are using:

set :scm, :git

The branch you want to deploy:

set :branch, 'master'

Number copies of the release you want to keep

set :keep_releases, 5

It’s possible to prompt the user for answers to some of these questions. For example, if you want to dynamically set the git branch name, do this:

ask :branch, proc { `git rev-parse --abbrev-ref HEAD`.chomp }

Some other options to format the output are:

set :format, :pretty
set :log_level, :debug
set :pty, true

It’s wise to setup the linked directories to avoid overwriting them on each deploy:

set :linked_dirs, %w{bin log tmp/pids tmp/cache tmp/sockets vendor/bundle public/system}

Step 6: Environment Configuration

Open the file corresponding to the chosen deployment environment. So, if we are deploying to production open the config/deploy/production.rb file and set the configuration options. If the environment file is not available simply create the file.

set :stage, :production

role :app, %w{deploy@<vps_ip_address>}
role :web, %w{deploy@<vps_ip_address>}
role :db,  %w{deploy@<vps_ip_address>}

Step 8: Puma Settings

Some plugins require their own configuation, and Puma is one of them. Add the following lines to the config/deploy.rb

set :puma_rackup, -> { File.join(current_path, 'config.ru') }
set :puma_state, "#{shared_path}/tmp/pids/puma.state"
set :puma_pid, "#{shared_path}/tmp/pids/puma.pid"
set :puma_bind, "unix://#{shared_path}/tmp/sockets/puma.sock"
set :puma_conf, "#{shared_path}/puma.rb"
set :puma_access_log, "#{shared_path}/log/puma_error.log"
set :puma_error_log, "#{shared_path}/log/puma_access.log"
set :puma_role, :app
set :puma_env, fetch(:rack_env, fetch(:rails_env, 'production'))
set :puma_threads, [0, 16]
set :puma_workers, 0
set :puma_init_active_record, true
set :puma_preload_app, true

Step 8: Deploy

To deploy your application, just run the deploy task:

$ bundle exec cap production deploy

Note that you have to specify the environment in every cap command.

Once deployment completes, visit “foobar.com” (or whatever you called it) in your browser.

Upgrading to Capistrano 3

Upgrading a Rails application to use capistrano 3 is very easy. The first step is to backup your old capistrano configuration files:

Capfile
config/deploy.rb

If you have multiple stages for your application, backup them also.

Now remove the old capistrano gem from Gemfile along with any related plugins. At this point, simply follow the previously mentioned steps in this article. Easy.

I hope this post helps you get onto Capistrano 3.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Ryan Le

    Thanks for this! There’s a lot of cap deployment guides out there.. but not many that cover Cap 3 so inconclusively.

  • Isaac Queiroz

    Very nice post, helped me a lot! I’m still trying to understand Puma (I’m from an older Apache+Passenger generation), but so far, so good!