As a developer who has worked extensively with Ruby on Rails across multiple organizations, I’ve learned that a smooth deployment process can save countless hours of headaches. In this post, I’ll walk through how to deploy a Rails application using Capistrano – a powerful deployment automation tool that makes the process reliable and repeatable.

Why Capistrano?

Before diving into the how-to, let’s briefly discuss why Capistrano is the go-to choice for Rails deployments:

  1. Simple rollbacks – If something goes wrong, you can revert to the previous working version with a single command
  2. Deploy from anywhere – Deploy your application from your local machine without logging into the server
  3. Automated tasks – From asset compilation to database migrations, Capistrano automates the entire process
  4. Multi-stage support – Easily manage deployments to different environments (staging, production, etc.)

Setting Up Your Production Environment

Before we can deploy with Capistrano, we need a properly configured server. Let’s set one up step by step.

1. Provision a Server

I typically use DigitalOcean for my production servers, but the same principles apply to any cloud provider:

  1. Create a new server (DigitalOcean calls them “droplets”)
  2. Wait about 60 seconds for server creation to complete
  3. Check your email for the root password
  4. SSH into your new server: ssh root@<your_server_ip>

2. Create a Deploy User

It’s a security best practice to create a dedicated user for deployments rather than using the root account:

adduser deploy
adduser deploy sudo
exit

Now let’s add your SSH key to the server for password-less login:

ssh-copy-id root@<your_server_ip>
ssh-copy-id deploy@<your_server_ip>

Let’s continue our setup as the deploy user:

bash

ssh deploy@<your_server_ip>

3. Install Ruby and Dependencies

First, let’s install the dependencies needed for Ruby, Rails, Node.js (for asset compilation), and Redis (for ActionCable):

bash

curl -sL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo add-apt-repository ppa:chris-lea/redis-server
sudo apt-get update
sudo apt-get install git-core curl zlib1g-dev build-essential libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev libcurl4-openssl-dev software-properties-common libffi-dev dirmngr gnupg apt-transport-https ca-certificates redis-server redis-tools nodejs yarn

Now let’s install Ruby using rbenv (a Ruby version manager):

git clone https://github.com/rbenv/rbenv.git ~/.rbenv
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
echo 'export PATH="$HOME/.rbenv/plugins/ruby-build/bin:$PATH"' >> ~/.bashrc
git clone https://github.com/rbenv/rbenv-vars.git ~/.rbenv/plugins/rbenv-vars
exec $SHELL

Install the Ruby version of your choice (I recommend using the same version as your development environment):

rbenv install 3.3.4
rbenv global 3.3.4
ruby -v  # Should output: ruby 3.3.4p0...

Finally, install Bundler:

gem install bundler
bundle -v  # Should output: Bundler version 2.x.x

If the bundle command isn’t found, run rbenv rehash and try again.

4. Configure NGINX and Passenger

We’ll use NGINX as our web server with Passenger to run our Ruby application:

sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 561F9B9CAC40B2F7
sudo sh -c 'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger $(lsb_release -cs) main > /etc/apt/sources.list.d/passenger.list'
sudo apt-get update
sudo apt-get install -y nginx-extras libnginx-mod-http-passenger
if [ ! -f /etc/nginx/modules-enabled/50-mod-http-passenger.conf ]; then sudo ln -s /usr/share/nginx/modules-available/mod-http-passenger.load /etc/nginx/modules-enabled/50-mod-http-passenger.conf ; fi

Now we need to configure Passenger to use the Ruby version we installed with rbenv:

sudo nano /etc/nginx/conf.d/mod-http-passenger.conf

Change the passenger_ruby line to:

passenger_ruby /home/deploy/.rbenv/shims/ruby;

Start NGINX:

sudo service nginx start

Now let’s configure our Rails application in NGINX:

sudo rm /etc/nginx/sites-enabled/default
sudo nano /etc/nginx/sites-enabled/myapp

Add the following configuration (replace myapp with your application name):

server {
  listen 80;
  listen [::]:80;

  server_name _;
  root /home/deploy/myapp/current/public;

  passenger_enabled on;
  passenger_app_env production;
  passenger_preload_bundler on;

  location /cable {
    passenger_app_group_name myapp_websocket;
    passenger_force_max_concurrent_requests_per_process 0;
  }

  # Allow uploads up to 100MB in size
  client_max_body_size 100m;

  location ~ ^/(assets|packs) {
    expires max;
    gzip_static on;
  }
}

Reload NGINX to apply the changes:

sudo service nginx reload

5. Create a PostgreSQL Database

PostgreSQL is my preferred database for production Rails applications:

sudo apt-get install postgresql postgresql-contrib libpq-dev
sudo su - postgres
createuser --pwprompt deploy
createdb -O deploy myapp
exit

You can test your database connection with:

psql -U deploy -W -h 127.0.0.1 -d myapp

Make sure to use 127.0.0.1 instead of localhost when connecting to PostgreSQL.

Setting Up Capistrano in Your Rails Application

Now let’s switch back to our local development machine to set up Capistrano.

1. Add Capistrano Gems

Add the following gems to your Gemfile:

group :development do
  gem 'capistrano', '~> 3.11'
  gem 'capistrano-rails', '~> 1.4'
  gem 'capistrano-passenger', '~> 0.2.0'
  gem 'capistrano-rbenv', '~> 2.1', '>= 2.1.4'
end

Run bundle to install these gems:

bundle install

2. Initialize Capistrano

Initialize Capistrano in your Rails application:

cap install STAGES=production

This will generate several files:

  • Capfile
  • config/deploy.rb
  • config/deploy/production.rb

3. Configure Capistrano

Edit your Capfile and uncomment or add these lines:

require 'capistrano/rails'
require 'capistrano/passenger'
require 'capistrano/rbenv'

set :rbenv_type, :user
set :rbenv_ruby, '3.3.4'  # Use the same Ruby version as on your server

Now update your config/deploy.rb file:

set :application, "myapp"  # Replace with your app name
set :repo_url, "git@github.com:username/myapp.git"  # Replace with your Git repo URL

# Deploy to the deploy user's home directory
set :deploy_to, "/home/deploy/#{fetch :application}"

append :linked_dirs, 'log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', '.bundle', 'public/system', 'public/uploads'

# Only keep the last 5 releases to save disk space
set :keep_releases, 5

Next, edit config/deploy/production.rb to specify your server:

server '<your_server_ip>', user: 'deploy', roles: %w{app db web}

4. Setting Up Environment Variables

Before our first deployment, we need to set up environment variables on the server:

ssh deploy@<your_server_ip>
mkdir -p /home/deploy/myapp
nano /home/deploy/myapp/.rbenv-vars

Add the necessary environment variables to this file:

DATABASE_URL=postgresql://deploy:PASSWORD@127.0.0.1/myapp
RAILS_MASTER_KEY=your_master_key_here
SECRET_KEY_BASE=your_secret_key_base_here
# Add any other environment variables your app needs

5. Deploy Your Application

Now we’re ready for our first deployment:

cap production deploy

The first deployment might take several minutes as Capistrano sets up the directory structure and installs all dependencies.

Understanding the Deployment Process

When you run cap production deploy, Capistrano performs the following steps:

  1. Clone your repository into a new release directory on the server
  2. Install dependencies with Bundler and Yarn
  3. Compile assets for production
  4. Run database migrations
  5. Create a symlink from the new release to the current directory
  6. Restart the application server

Handling Deployments in the Real World

Rolling Back Deployments

If something goes wrong with a deployment, you can easily roll back to the previous version:

cap production deploy:rollback

Managing Database Migrations

By default, Capistrano runs migrations during each deployment. If you want to skip migrations for a specific deployment:

cap production deploy SKIP_MIGRATION=true

Running Tasks on the Server

You can run arbitrary rake tasks on the server:

cap production rails:rake:task[db:seed]

Checking Logs

To troubleshoot deployment issues, check your logs:

# Rails logs
less /home/deploy/myapp/current/log/production.log

# NGINX and Passenger logs
sudo less /var/log/nginx/error.log

Advanced Capistrano Customization

Capistrano is highly customizable. Here are a few common customizations:

Adding Custom Tasks

You can add custom tasks by editing your config/deploy.rb file. For example, to restart Sidekiq after deployment:

namespace :deploy do
  desc 'Restart Sidekiq'
  task :restart_sidekiq do
    on roles(:app) do
      execute :sudo, :systemctl, :restart, :sidekiq
    end
  end
  
  after :publishing, :restart_sidekiq
end

Setting Up SSL with Let’s Encrypt

For production applications, you should use HTTPS. You can use Let’s Encrypt with Certbot:

sudo apt-get install certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.com

Configuring Multi-Stage Deployments

If you need separate staging and production environments:

cap install STAGES=staging,production

Then configure each environment in their respective files in the config/deploy/ directory.

Conclusion

Capistrano transforms the Rails deployment process from a manual, error-prone task into an automated, repeatable procedure. By setting up your server correctly and configuring Capistrano properly, you’ll be able to deploy with confidence and focus on what matters most – building features for your application.

The setup process might seem lengthy at first, but once you’ve gone through it once, subsequent deployments become trivially simple. That single command – cap production deploy – represents hours of manual work compressed into a few minutes of automated magic.

Happy deploying!

Leave a Reply

Your email address will not be published. Required fields are marked *