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:
- Simple rollbacks – If something goes wrong, you can revert to the previous working version with a single command
- Deploy from anywhere – Deploy your application from your local machine without logging into the server
- Automated tasks – From asset compilation to database migrations, Capistrano automates the entire process
- 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:
- Create a new server (DigitalOcean calls them “droplets”)
- Wait about 60 seconds for server creation to complete
- Check your email for the root password
- 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:
- Clone your repository into a new release directory on the server
- Install dependencies with Bundler and Yarn
- Compile assets for production
- Run database migrations
- Create a symlink from the new release to the currentdirectory
- 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!
 
								