Rails: Capistrano for easy deployment and configuration to Ubuntu with Nginx and Puma

In this post I’ll demonstrate how to use Capistrano to deploy and configure a Rails app to an Ubuntu Server with Nginx and Puma.

Table of Contents

Setup the Ubuntu Server

Login to the server as root user.

ssh root@example.emmanuelcorrales.com

Create a super user called deploy.

useradd -m deploy -G sudo

Login as deploy.

su - deploy

Update Ubuntu then install Curl, Git and Nginx.

sudo apt-get update
sudo apt-get install curl git-core nginx -y

Setup Ruby on Rails environment

Install RVM.

gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
curl -sSL https://get.rvm.io | bash -s stable
source /home/deploy/.rvm/scripts/rvm
rvm requirements

Install Ruby via RVM.

rvm install 2.2.2
rvm use 2.2.2 --default
ruby --version

Install Rails and Bundler without docs to save space and nobody needs docs on the server.

deploy@server:~:$ gem install rails -v '5.2.0' -V --no-ri --no-rdoc
deploy@server:~:$ gem install bundler -V --no-ri --no-rdoc

Setup SSH

Create .ssh directory

deploy@server:~:$ mkdir -m 700 .ssh
deploy@server:~:$ touch ~/.ssh/authorized_keys
deploy@server:~:$ chmod 600 ~/.ssh/authorized_keys

Setup SSH access to the server. Create key pairs from your local machine.

ssh-keygen -t rsa

This will generate two files ~/.ssh/id_rsa and ~/.ssh/id_rsa.pub. Copy the public key ~/.ssh/id_rsa it to the server.

ssh-copy-id deploy@example.emmanuelcorrales.com

The server will also use the same public key to pull changes to the Rails from the repository. Add the newly created public key (~/.ssh/id_rsa.pub) to your repository’s deployment keys. If you are using Github you can find the instructions here.

Setup Capistrano

“Capify” the Rails app

Add this gem to your Gemfile.

group :development do
  gem 'capistrano', '~>3.10', require: false
end

Then install it.

bundle install

From your apps local root directory generate the necessary configuration files.

bundle exec cap install

This command will generate four files Capfile, config/deploy.rb config/deploy/staging.rb and config/deploy/production.rb.

We want to deploy the Rails app called example.emmanuelcorrales.com whose repository is hosted at git@github.com:EmmanuelCorrales/example.git. Different verisons of the app would be stored at the /home/default/example.emmanuelcorrales.com directory. Configure Capistrano to install it there by changing the contents of config/deploy.rb to look like the code below.

# config valid for current version and patch releases of Capistrano
lock "~> 3.11.0"

set :application, 'example.emmanuelcorrales.com'
set :repo_url, 'git@github.com:EmmanuelCorrales/example.git'

# Deploy configurations
set :deploy_via,      :remote_cache
set :deploy_to,       "/home/#{fetch(:user)}/apps/#{fetch(:application)}"

Add Ruby on Rails deployment tasks

Add this gem to your Gemfile under the capistrano gem.

gem 'capistrano-rails', '~>1.4', require: false

Then install it.

bundle install

Edit the Capfile to look like this:

# Load DSL and set up stages
require "capistrano/setup"

# Include default deployment tasks
require "capistrano/deploy"

# Load the git SCM plugin appropriate to your project:
require "capistrano/scm/git"
install_plugin Capistrano::SCM::Git

require "capistrano/rails"

# Load custom tasks from `lib/capistrano/tasks` if you have any defined
Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r }

Symlink Rails shared files and directories like log, tmp and public/uploads. Enable it by setting the linked_dirs and linked_files options at the deploy.rb.

## Linked Files & Directories (Default None):
append :linked_dirs, 'log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', '.bundle', 'public/system', 'public/uploads'
append :linked_files, 'config/database.yml', 'config/secrets.yml', 'config/application.yml'

Configure Puma

Add this gem to your Gemfile under the capistrano gem.

gem 'capistrano3-puma', require: false

Then install it.

bundle install

Add these lines to your Capfile.

require 'capistrano/puma'
install_plugin Capistrano::Puma

Add these lines to the config/deploy.rb.

# Puma configurations
set :puma_threads, [4, 16]
set :puma_workers, 0
set :puma_bind, "unix://#{shared_path}/tmp/sockets/#{fetch(:application)}-puma.sock"
set :puma_state, "#{shared_path}/tmp/pids/puma.state"
set :puma_pid, "#{shared_path}/tmp/pids/puma.pid"
set :puma_access_log, "#{release_path}/log/puma.error.log"
set :puma_error_log, "#{release_path}/log/puma.access.log"
set :puma_preload_app, true
set :puma_worker_timeout, nil
set :puma_init_active_record, true

Configure Nginx

Add this line to your Capfile below the line install_plugin Capistrano::Puma.

install_plugin Capistrano::Puma

Add these lines to the config/deploy.rb. These are the parameters for the Nginx configurations that will be uploaded to the server.

# Nginx configurations
set :nginx_config_name, "#{fetch(:application)}_#{fetch(:stage)}"
set :nginx_flags, 'fail_timeout=0'
set :nginx_http_flags, fetch(:nginx_flags)
set :nginx_server_name, "localhost #{fetch(:application)}.local"
set :nginx_sites_available_path, '/etc/nginx/sites-available'
set :nginx_sites_enabled_path, '/etc/nginx/sites-enabled'
set :nginx_socket_flags, fetch(:nginx_flags)
set :nginx_ssl_certificate, "/etc/ssl/certs/#{fetch(:nginx_config_name)}.crt"
set :nginx_ssl_certificate_key, "/etc/ssl/private/#{fetch(:nginx_config_name)}.key"
set :nginx_use_ssl, false

Upload the nginx configuration to the server.

bundle exec cap production puma_nginx:config

Deploying the Rails app with Capistrano

Edit the contents of config/deploy/production.rb to look like this:

server "example.emmanuelcorrales.com", user: "deploy", roles: %w{web app db}

Deploy to the server.

bundle exec cap production deploy