This post will show you steps on how to deploy the ruby rails app on the AWS server running apache and phusionpassenger using Capistrano. As you know Capistrano is a widely used deployment automation tool for ruby applications. It uses ‘ssh’ to deploy from the development server or staging server to the production server. There is other CI/CD software deployment automation tool like ‘mina’ but Capistrano is famous for ruby on rails application because of its support for asset pipeline and database migration.

Contents

Prerequisites

On the server side – I am using Ubuntu 18.04.4 LTS AWS instance along with “ubuntu” user – sudo privileges on the server side. I will be using ‘ubuntu’ as a deployment user, you can create a new user as ‘deployer’ and do the deployment using the new user. I am using ‘Phusion Passenger 6.0.4’ and Apache/2.4.29 (Ubuntu) server.

On local machine – I am using Ubuntu-20.04 – WSL on windows 10 as a local machine, while you can have Ubuntu desktop or laptop but, I wanted to try out with WSL-Windows Subsystem for Linux.

Step 1- Installing Ruby On Windows

The first thing we need is ruby, for ruby installation we will use RVM- Ruby Version Manager. It allows us to install multiple Ruby versions at the same time. A quick glance over their official website reveals that installing RVM is pretty easy.  There are lots of them to choose from (rbenv, chruby, etc.), we’ll use RVM for this post. RVM allows you to easily install and manage multiple rubies on the same system and use the correct one according to your app. You need software-properties-common installed in order to add ppa repositories. If ruby is not already installed then run the following commands else you can skip to step-2.

$ sudo apt-get install software-properties-common

Reading package lists… Done
Building dependency tree
Reading state information… Done
software-properties-common is already the newest version (0.98.9.5).
software-properties-common set to manually installed.
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
$ sudo apt-add-repository -y ppa:rael-gc/rvm
$ sudo apt-get update
$ sudo apt-get install rvm
Setting up rvm (1.29.12-1) ...
Creating group 'rvm'
Installing RVM to /usr/share/rvm/
Installation of RVM in /usr/share/rvm/ is almost complete:

  * First you need to add all users that will be using rvm to 'rvm' group,
    and logout - login again, anyone using rvm will be operating with `umask u=rwx,g=rwx,o=rx`.

  * To start using RVM you need to run `source /etc/profile.d/rvm.sh`
    in all your open shell windows, in rare cases you need to reopen all shell windows.
  * Please do NOT forget to add your users to the rvm group.
     The installer no longer auto-adds root or users to the rvm group. Admins must do this.
     Also, please note that group memberships are ONLY evaluated at login time.
     This means that users must log out then back in before group membership takes effect!
Thanks for installing RVM ?
Please consider donating to our open collective to help us maintain RVM.

Add your user to RVM group

$ sudo usermod -a -G rvm $USER
$ rvm -v
rvm 1.29.12 (manual) by Michal Papis, Piotr Kuczynski, Wayne E. Seguin [https://rvm.io]

Install ruby via RVM

All right, nice. Now that we have RVM installed, we are ready to install any version of Ruby that we want. Let’s see what’s available by running this: rvm list known

Install the latest ruby

$ rvm install ruby 3.0.0
$ ruby -v
ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-linux]

Step 2- Install Rails and Bundler on Windows

Once you have installed ruby on the windows by following the previous step, you can now install rails gem, this will allow you to run, and debug the ruby application. Also, I will show you how to install a bundler on windows, bundler is ruby gem that reads “Gemfile” and installs all the required gems.

$ rails -v
Command 'rails' not found, but can be installed with:

sudo apt install ruby-railties

Command ‘rails’ not found, but can be installed as follows –

$ gem install rails -V
Installing ri documentation for rails-6.1.4.1
Done installing documentation for rack, concurrent-ruby, sprockets, zeitwerk, tzinfo, i18n, activesupport, nokogiri, crass, loofah, rails-html-sanitizer, rails-dom-testing, rack-test, erubi, builder, actionview, actionpack, sprockets-rails, thor, method_source, railties, mini_mime, marcel, activemodel, activerecord, globalid, activejob, activestorage, actiontext, mail, actionmailer, actionmailbox, websocket-extensions, websocket-driver, nio4r, actioncable, rails after 168 seconds
37 gems installed

This will take a while to install as it installs rails documentation, you can skip the rails documentation step to install faster and save some disk space using –no-ridoc flag. The installation command fetches the latest stable rails version and installs it, you can install any version of rails with -v flag as follows –

$ gem install rails -v '4.2.0'

Next install bundler gem.

$ gem install bundler

Step 3- Install Capistrano On Windows

If you have the application ready along with ‘Gemfile” then you can directly run bundler update to install ‘Capistrano, else you can follow the official guide to install capistrano. I am going to update my ‘Gemfile” and do bundler update as per the github page.

gemfile for capistrano

Once you have updated your Gemfile, run bundler to ensure Capistrano is downloaded and installed:

$ bundle install

Fetching capistrano 3.16.0
Installing capistrano 3.16.0
Fetching capistrano-bundler 2.0.1
Installing capistrano-bundler 2.0.1
Fetching capistrano-passenger 0.2.1
Installing capistrano-passenger 0.2.1
Fetching capistrano-rails 1.6.1
Installing capistrano-rails 1.6.1
Fetching capistrano-rvm 0.1.2
Installing capistrano-rvm 0.1.2

Once Capistrano is installed you can verify using cap -T

$ cap -T 
cap bundler:clean                  # Remove unused gems installed by bundler
cap bundler:config                 # Configure the Bundler environment for the release so that subequent
cap bundler:install                # Install the current Bundler environment
cap bundler:map_bins               # Maps all binaries to use `bundle exec` by default
cap deploy                         # Deploy a new release
cap deploy:check                   # Check required files and directories exist
cap deploy:check:directories       # Check shared and release directories exist
cap deploy:check:linked_dirs       # Check directories to be linked exist in shared
cap deploy:check:linked_files      # Check files to be linked exist in shared
cap deploy:check:make_linked_dirs  # Check directories of files to be linked exist in shared
cap deploy:cleanup                 # Clean up old releases
cap deploy:cleanup_assets          # Cleanup expired assets

For the first time you will have to cpify the project, run following command, in my case, I had already done the setup so it says capfile already exists, go ahead and capify your project.

$ bundle exec cap install

mkdir -p config/deploy
[skip] config/deploy.rb already exists
[skip] config/deploy/staging.rb already exists
[skip] config/deploy/production.rb already exists
mkdir -p lib/capistrano/tasks
[skip] Capfile already exists
Capified

This will create following file structure in your app

├── Capfile
├── config
│   ├── deploy
│   │   ├── production.rb
│   │   └── staging.rb
│   └── deploy.rb
└── lib
    └── capistrano
            └── tasks

In our case we are not using staging server so, we will not be updating ‘staging.rb’ file. We will be updating ‘production.rb’. In your project, if you are using a staging server then you will update ‘staging.rb’ as per your needs.

Step 4 – Update Deployment Configurations on Local Machine

Update Capfile in the root directory of your Rails app as follows, I am using ‘git’ as SCM – source code management, if you are using SVN or Hg please enable SCM plugin as per your need. Also, I am using ‘passenger’ so have enabled the same module, if you are using ‘puma’ you have to enable ‘puma’ instead of ‘passenger’

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

# Include default deployment tasks
require "capistrano/deploy"

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


# Include tasks from other gems included in your Gemfile
#
#
require "capistrano/rvm"
# require "capistrano/rbenv"
# require "capistrano/chruby"
require "capistrano/bundler"
require 'capistrano/rails'
require "capistrano/passenger"

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

The Capfile  does pre-load tasks automatically like selecting correct ruby, pre-compile assets, cloning github repo to correct location and installing any new dependency if your Gemfile is changed.

Next is to update ‘config/deploy/production.rb‘ file as follows. This file contains your ssh setting for AWS server and default rails environment details. The code marked and underlined is specific to your AWS production server, please update it with your server details. For example, AWS server name, PEM file path, and name. Most of the AWS server names are lengthy but you can give the AWS server public IP address as well. The default user I am using is Ubuntu, as mentioned in earlier sections, this can be your separate ‘ deplyoment’ user as well.

# server-based syntax
# ======================
# Defines a single server with a list of roles and multiple properties.
# You can define all roles on a single server, or split them:
set :rails_env, "production"
set :bundle_without, %w{test}.join(':')

set :default_environment, {
  'PATH' => "$PATH",
  'RUBY_VERSION' => 'ruby-2.7.0',
  'GEM_HOME' => '/home/ubuntu/.rvm/gems/ruby-2.7.0',
  'GEM_PATH' => '/home/ubuntu/.rvm/gems/ruby-2.7.0:/home/ubuntu/.rvm/gems/ruby-2.7.0@global'
}

# role-based syntax
# ==================

# role :app, %w{ubuntu@ec2-18-237-189-231.us-west-2.compute.amazonaws.com}
role :web, %w{ubuntu@ec2-18-237-189-231.us-west-2.compute.amazonaws.com}
# role :db,  %w{ubuntu@ec2-18-237-189-231.us-west-2.compute.amazonaws.com}

# Configuration
# =============

# Custom SSH Options
# ==================
server "ec2-18-237-189-231.us-west-2.compute.amazonaws.com",
  user: "ubuntu",
  roles: %w{web},
  ssh_options: {
    user: "ubuntu", # overrides user setting above
    keys: ["#{Dir.home}/.ssh/keys/app/production/app_pemfile.pem"],
    forward_agent: false,
    auth_methods: %w(publickey)
  }

The last step is to update ‘config/deploy.rb‘ with the deployment details. The deploy.rb file contains information related to release management and rollback tasks. The deploy.rb automatically manages multiple releases, checks if the remote github repo is up to date, restarts passenger, reload the server, maintain logs.

set :application, "Yourappname"
set :repo_url, "git@github.com:YourGitRepo/abc.git"

# Default branch is :master
set :branch, 'master'

# Default deploy_to directory is /var/www/my_app_name
# set :deploy_to, "/var/www/my_app_name"

# Default value for :format is :airbrussh.
# set :format, :airbrussh

# You can configure the Airbrussh format using :format_options.
# These are the defaults.
# set :format_options, command_output: true, log_file: "log/capistrano.log", color: :auto, truncate: :auto

# Default value for :pty is false
# set :pty, true

set :linked_files, fetch(:linked_files, []).push('config/database.yml', 'config/secrets.yml')

set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'public/spree', 'public/assets')
# set :linked_dirs, fetch(:linked_dirs, [])

set :rvm_ruby_version, '2.7.0'

# Default value for default_env is {}
# set :default_env, { rails_env: "development" }
set :default_environment, {
  'PATH' => "$PATH",
  'RUBY_VERSION' => 'ruby-2.7.0',
  'GEM_HOME' => '/home/ubuntu/.rvm/gems/ruby-2.7.0',
  'GEM_PATH' => '/home/ubuntu/.rvm/gems/ruby-2.7.0:/home/ubuntu/.rvm/gems/ruby-2.7.0@global',
  'PASSENGER_INSTANCE_REGISTRY_DIR' => '/home/ubuntu/passenger_temp'
}
# Default value for local_user is ENV['USER']
# set :local_user, -> { `git config user.name`.chomp }

# Default value for keep_releases is 5
set :keep_releases, 2

# Uncomment the following to require manually verifying the host key before first deploy.
# set :ssh_options, verify_host_key: :secure

set :migration_role, :web
set :migration_command, 'db:migrate'
set :conditionally_migrate, true

append :linked_files, "config/master.key"

# namespace :deploy do
#   after "bundler:install", "assets:precompile"
# end

# namespace :assets do
#   desc "Precompile assets locally and then rsync to web servers"
#   task :precompile do
#     on roles(:app) do
#       rsync_host = host.to_s # this needs to be done outside run_locally in order for host to exist
#       run_locally do
#         with rails_env: fetch(:stage) do
#           execute :bundle, "exec rake assets:precompile"
#         end
#         execute "rsync -av public/assets/ ubuntu@ec2-18-237-189-231.us-west-2.compute.amazonaws.com:#{current_path}/public/assets/"
#         execute "rm -rf public/assets"
#         execute "rm -rf tmp/cache/assets" # in case you are not seeing changes
#       end
#     end
#   end
# end

namespace :deploy do
  namespace :check do
    before :linked_files, :set_master_key do
      on roles(:web), in: :sequence, wait: 10 do
        unless test("[ -f #{shared_path}/config/master.key ]")
          upload! 'config/master.key', "#{shared_path}/config/master.key"
        end
      end
    end
  end

First Check till now

Once you have updated config files, you can check the deployment by using ‘cap doctor’ command.

$ cap production doctor
ruby-2.7.0
ruby 2.7.0p0 (2019-12-25 revision 647ee6f091) [aarch64-linux]

Environment

    Ruby     ruby 2.7.0p0 (2019-12-25 revision 647ee6f091) [x86_64-linux]
    Rubygems 3.1.2
    Bundler  2.1.2
    Command  /usr/share/rvm/gems/ruby-2.7.0/bin/cap production doctor

Gems

    capistrano           3.16.0
    airbrussh            1.4.0
    rake                 13.0.6
    sshkit               1.21.2
    net-ssh              6.1.0
    capistrano-bundler   2.0.1
    capistrano-passenger 0.2.1
    capistrano-rails     1.6.1
    capistrano-rvm       0.1.2

Variables

    :application                     "Yourappname"

This command will list out the production server details.

Step 5- Setting up SSH keys On Production Server

I will be using SSH key for authorization with github and production server. You need to add public key to the SSH keys in github, this is required because SSH agent forwarding requires the installation of public keys on all target systems.

Now login to your production server and generate SSH key.

$ ssh -T git@github.com
git@github.com: Permission denied (publickey).
ubuntu@ip-172-31-4-113:~$ ssh-keygen -t ed25519 -C "yourgithubaccount@domain.com"
$ cat /home/ubuntu/.ssh/id_ed25519.pub
$ ssh -T git@github.com
Hi neudeeptech/vtpbook! You've successfully authenticated, but GitHub does not provide shell access.

Next follow the detailed instructions for Github repo to add this SSH key to github deployment key of your repo. If you are using other SCM tool like bitbucket, gitlab then follow the instructions as per their guide.

NOTE: If you do these changes on your local development machine then you will get publickey error almost every time.

You don’t need to clone or pull the git repository on your local development setup for any new changes. Capistrano will handle all that for you automatically on production server.

Step 6 – Now You Can Setup Access

This is last step in this lengthy article, you will have to setup access rights to the ‘deployment’ user in this case ‘ubuntu’ to the release directory mentioned in ‘deploy.rb’

$ cap production deploy:check:directories
      01 mkdir: cannot create directory ‘/var/www/my_app_name’: Permission denied

This will list out the production server directories if it has access or not. For the first time, you will have to create these folders with ‘sudo’ and change the ownership to ‘ubuntu’ user. Login to the production server and run the following commands

$ mkdir -p /var/www/my_app_name
$ sudo chown ubuntu: /var/www/my_app_name

Step 7- See How Easily You Can Deploy Application 🙂

On your local machine confirm if everything is setup properly with deploy:check, this will check for git, directory access.

$ cap production deploy:check

If everything is good then do dry run first before you deploy the application to simulate deploying to the production environment but actually, it will not deploy yet.

$cap production deploy --dry-run

This will perform tasks like

You are ready with your first deployment, from your local machine do the deployment.

$cap production deploy 

This will take some time based on your gems, it’s time for a nice coffee break.

Hope you have enjoyed reading this article on How To Deploy Ruby App On AWS With Capistrano.

Troubleshooting Capistrano Errors

fatal error: libpq-fe.h: No such file or directory

rails install pg gem fails.

$ sudo apt-get install libpq-dev

git@github.com: Permission denied (publickey).

If you are setting up github for first time then you will see publickey permission denied error

00:03 git:check
      01 git ls-remote git@github.com:neudeeptech/vtpbook.git HEAD
      01 Warning: Permanently added 'github.com,192.30.255.113' (RSA) to the list of known hosts.
      01 git@github.com: Permission denied (publickey).
      01 fatal: Could not read from remote repository.
      01
      01 Please make sure you have the correct access rights
      01 and the repository exists.

Well How to solve Permission denied (publickey) error when using Git, the solution worked out for me is, generate public key and copy this public key to github.com Github => Profile=> Settings => SSH and GPG keys => paste key

check status $ ssh -T git@github.com
git@github.com: Permission denied (publickey).
legacy system $ ssh-keygen -t rsa -b 4096 -C "yourgithubemailid@test.com"
Generating public/private rsa key pair.
Enter file in which to save the key (/home/bhagwat/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/bhagwat/.ssh/id_rsa
Your public key has been saved in /home/bhagwat/.ssh/id_rsa.pub

With Ed25519 algorithm $ ssh-keygen -t ed25519 -C "your_email@example.com"

$ explorer.exe .
check status  $ ssh -T git@github.com
Hi neudeep18! You've successfully authenticated, but GitHub does not provide shell access.

Follow the detailed instructions for Github repo to add the newly created public key (~/.ssh/id_rsa.pub) to deploy keys.

SSHKit::Command::Failed: git exit status: 128

Still Could not read from the remote git repository because the above solution will work on Ubuntu native system but I am using Windows Subsystem for Linux version 2 (WSL 2) as my personal development environment so there are a few more steps.

$ chmod 600 ~/.ssh/id_rsa

Errno::ENOENT: No such file or directory @ rb_file_s_stat – config/master.key

On your local machine generate master.key. This is required from the latest Capistrano versions and as per the ‘deploy.rb’ it will copy from your local machine to the production server, so just run the following command

$ EDITOR=vim rails credentials:edit

Stack Smashing Detected

*** stack smashing detected ***: terminated

While doing deploy:assets:precompile stage this error was popping up. This is because there is no support yet for ‘mini_racer’ and ‘libv8_node’ gem for AWS ‘aarch64’ instance. They work fine on x86_64 target instance. So either you can get the x86_64 target AWS instance or for now disable these two gems in your ‘Gemfile’.

OpenSSL::Cipher::CipherError:

assets precompile task complain about this error if you have not copied proper ‘master.key’ file in your ‘config’ folder. Make sure you have the same file while you have generated when you created ruby app for the first time.

Class level instance variable was not initialized

When we deploy in the development environmet, it does not cache the class so the class level instance variable was reinitializing on each page load. This causes your application to work in a local environment but when you deploy a few class variables complain as not initialized. So change the setting in the config/environments/development.rb file to cache classes.

One Response