| Work | Research and Development (R&D) | Engineering | Infrastructure | How to deploy a Rails app using Digital Ocean
How to deploy a Rails app using Digital Ocean

Overview

This is an adapted guide of the GoRails guide for Deploy Ruby On Rails: Ubuntu 22.04 Jammy Jellyfish in 2022

Create New Droplet

2GB Memory, 1 CPU, 50GB SSD, 2TB Transfer Ubuntu 22.04 (LTS) x64. Select your SSH Keys. Enable Backups, Monitoring, IPV6.
Choose a hostname as [project name]-web-prd

SSH Into Droplet

Add New SSH Key. In local terminal copy your ssh key and paste into Digital Ocean:

pbcopy < ~/.ssh/id_rsa.pub

After droplet is created, copy droplet IP and ssh in from local

ssh root@IP_ADDRESS

Create Deploy User

sudo adduser deploy

Add sudo permissions to Deploy User

sudo adduser deploy sudo
su deploy

Add SSH Key For Deploy

There are two ways to add SSH keys (1) Using ssh-copy-id (2) Manually adding your public key as an authorized key

Using ssh-copy-id

On server, add SSH key from deploy user by opening password auth

sudo nano /etc/ssh/sshd_config

Change 'PasswordAuthentication yes'

sudo service ssh stop
sudo service ssh start

On local

$ ssh-copy-id deploy@ip_address

On server, close password auth

sudo nano /etc/ssh/sshd_config

Change 'PasswordAuthentication no'

sudo service ssh stop
sudo service ssh start

On local, test ssh using the deploy user

ssh deploy@ip_address

Using authorized-key

On local, copy your public key

// for RSA SSH key
cat ~/.ssh/id_rsa.pub

// for ED25519 SSH key
cat ~/.ssh/id_ed25519.pub

On server, ensure you're logged in as a deploy user and create an authorized_keys file

mkdir ~/.ssh && touch ~/.ssh/authorized_keys

Open your authorized_keys file

sudo nano ~/.ssh/authorized_keys

Paste in your public key and save

ssh-ed25519 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX user@computer

On local, test ssh using the deploy user

ssh deploy@ip_address

Install Node

# Adding Node.js repository
curl -sL https://deb.nodesource.com/setup_16.x | sudo -E bash -
# Adding Yarn repository
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
# Refresh our packages list with the new repositories
sudo apt-get update
# Install our dependencies for compiiling Ruby along with Node.js and Yarn
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
node -v
# Node v16.19.0

Install Ruby and rbenv for managing Ruby versions

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
rbenv install 3.0.3
rbenv global 3.0.3
ruby -v
# ruby 3.0.3

Install Bundler

gem install bundler

rbenv users need to run rbenv rehash after installing bundler.

rbenv rehash

Install Nginx and Passenger

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 focal 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
sudo ls /etc/nginx/conf.d/mod-http-passenger.conf

Edit passenger.conf

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

Change the passenger_ruby line to point to your ruby executable:

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

Restart Nginx

sudo service nginx start

Visit your server's IP address in the browser and you should see a Nginx message.

Next, update Nginx server settings.

Remove the existing nginx server file

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

Create a new one

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

Paste in the following contents

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

  server_name myapp.com www.myapp.com;
  root /home/deploy/myapp/current/public;

  passenger_enabled on;
  passenger_app_env production;

  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;
  }
}

Note that if you don't have a domain name available yet, you can instead put a placeholder in server_name _;

Restart server

sudo service nginx reload

Install Postgres

sudo apt-get install postgresql postgresql-contrib libpq-dev

Create Database:

sudo su - postgres
createuser --pwprompt deploy
createdb -O deploy myapp_production
exit

Test Database Login:

psql myapp_production deploy

If Needed, Change Database Password

ALTER USER deploy WITH PASSWORD 'new_password';

Install Capistrano

In your app Gemfile under the development environment, add capistrano:

group :development do
  # Deployment
  gem 'capistrano', '~> 3.17'
  gem 'capistrano-rails', '~> 1.6', '>= 1.6.2'
  gem 'capistrano-passenger', '~> 0.2.1'
  gem 'capistrano-rbenv', '~> 2.2'
  gem 'ed25519', '~> 1.3'
  gem 'bcrypt_pbkdf', '~> 1.1'
end
bundle install

Commit and push your code git commit -m "Add capistrano gem"

cap install STAGES=production

Modify Capfile by adding the following just above # Load custom tasks from lib/capistrano/tasks if you have any defined

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

set :rbenv_type, :user
set :rbenv_ruby, '3.0.3'

Modify config/deploy/production.rb by adding:

server 'your_ip_address', user: 'deploy', roles: %w{app db web}

Modify config/deploy.rb by adding:

set :application, 'my_app_name'
set :repo_url, 'git@gitlab.com:username/my_app_name.git'

# Default branch is :master
ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp

# Default deploy_to directory is /var/www/my_app_name
set :deploy_to, '/home/deploy/my_app_name'

# Default value for :linked_files is []
append :linked_dirs, 'log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'tmp/webpacker', 'vendor', '.bundle', 'public/system', 'public/uploads', 'storage'

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

Commit and push your code git commit -m "Configure capistrano"

Perform a test deployment

// for RSA SSH key
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_rsa
bundle exec cap production deploy

// for ED25519 SSH key
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
bundle exec cap production deploy

Add platform support

Your deployment may fail due to a different platform being used on your local machine versus the cloud virtual machine. When deploying, I encountered an error:

DEBUG [b4677b9e] Your bundle only supports platforms ["x8664-darwin-21"] but your local platform is x8664-linux. Add the current platform to the lockfile with bundle lock --add-platform x86_64-linux and try again.

To resolve this error, I ran bundle lock --add-platform x86_64-linux to modify my Gemfile.lock.

Commit and push your code git commit -m "Add x86_64-linux to gem lock" and redeploy.

Configure your environment variables

Your deployment is likely to fail again but this time due to your environment variables not being set.

In your application's config/database.yml point your production database to use environment variables.

production:
  <<: *default
  database: <%= ENV["MY_APP_DATABASE_DATABASE"] %>
  host: <%= ENV["MY_APP_DATABASE_HOST"] %>
  username: <%= ENV["MY_APP_DATABASE_USERNAME"] %>
  password: <%= ENV["MY_APP_DATABASE_PASSWORD"] %>

Commit and push your code git commit -m "Update production database.yml"

Next, set your environment variables on the server. SSH into the server and open nano /home/deploy/myapp/.rbenv-vars. Add in the following:

RAILS_MASTER_KEY=your_rails_master_key
SECRET_KEY_BASE=your_secret_key

MY_APP_DATABASE_DATABASE=myapp_production
MY_APP_DATABASE_HOST=127.0.0.1
MY_APP_DATABASE_USERNAME=deploy
MY_APP_DATABASE_PASSWORD=your_database_password

For your_rails_master_key, you can reuse your local application's config/master.key. Note that this file is typically not checked into source control.

You can generate a your_secret_key by running rails secret from your terminal. If you encounter any errors when deploying your application asking you for a secret of a certain bit length, you can also generate a secret by opeining your rails console and running SecureRandom.hex(16).

Re-deploy

Re-run deployment. This time it should pass. Note the first successful deployment will take longer then usual as all gems will be installed.

bundle exec cap production deploy

Restart Site

touch my_app_name/current/tmp/restart.txt

That's it! Your rails application should now be live. To see your server logs in real time, SSH into the server and run.

tail -f my_app_name/current/log/production.log

Handling errors

*Phusion Passenger doesn't seem to be running *

If during your deploy you encounter an error such as *** ERROR: Phusion Passenger(R) doesn't seem to be running, you can check what the error might be by viewing nginx logs sudo tail -f /var/log/nginx/error.log

An example of an error I encountered was:

The application encountered the following error: You have already activated strscan 3.0.1, but your Gemfile requires strscan 3.0.4. Since strscan is a default gem, you can either remove your dependency on it or try updating to a newer version of bundler that supports strscan as a default gem. (Gem::LoadError)

To resolve this, I ended up running gem install strscan 3.0.4. Afterwards, I visited the server's IP address in my browser and was able to be routed to the Rails application.

PassengerAgent: error while loading shared libraries: libcrypto.so.1.1

Another example on Ubuntu 22.04

PassengerAgent: error while loading shared libraries: libcrypto.so.1.1: cannot open shared object file: No such file or directory
2022/00/00 00:00:00 [alert] 37777#37777: Unable to start the Phusion Passenger(R) watchdog: it seems to have crashed during startup for an unknown reason, with exit code 127 (-1: Unknown error)

To resolve this I downloaded and installed the missing package using wget http://nz2.archive.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2.16_amd64.deb and sudo dpkg -i libssl1.1_1.1.1f-1ubuntu2.16_amd64.deb.

See this StackOverflow page for more details

Error opening Passengerfile.json

2023/01/22 00:07:55 [alert] 40571#40571: *6 Error opening '/home/deploy/myapp/current/Passengerfile.json' for reading: Permission denied (errno=13); This error means that the Nginx worker process (PID 40571, running as UID 33) does not have permission to access this file. Please read this page to learn how to fix this problem: https://www.phusionpassenger.com/library/admin/nginx/troubleshooting/?a=upon-accessing-the-web-app-nginx-reports-a-permission-denied-error; Extra info, client: 0.0.0.0, server: myapp.com, request: "GET / HTTP/1.1", host: "myapp.com"

To resolve this become the root user and grant permissions to the deploy user sudo -i, cd /home and chmod g+x,o+x deploy.

https://stackoverflow.com/questions/56357468/nginx-passenger-integration-mode-always-require-passengerfile-json-file

Additional Options

Install RMagick

apt-get install libmagickwand-dev # afterwards cap deploy carrierwave commit to production