How to Deploy a Rails 6 Application with Capistrano, Nginx, Puma, Postgresql, LetsEncrypt on Ubuntu 20.04
Published: 2021-05-31 18:59:37

---
title: How to Deploy a Rails 6 Application with Capistrano, Nginx, Puma, Postgresql, LetsEncrypt on Ubuntu 20.04
published: true
date: 2021-05-31 18:14:11 UTC
tags: 
canonical_url: https://webdevchallenges.com/how-to-deploy-a-rails-6-application-with-capistrano
---

# What are we going to do

So whenever I start a new project and want to deploy it to production, I need to research from scratch how to setup a Ubuntu Server including Firewall, how to setup Capistrano, get NGINX to work with Puma etc. That’s why I summarize everything I do to get a Project deployed.

# Create the Rails project

Make sure you have postgresql installed and running locally.

```
rails -v
# Rails 6.1.3.2
rails new mysite --database=postgresql
rake db:setup
rails db:migrate
rails s

```

Now you should be able to visit [http://localhost:3000](http://localhost:3000) in your browser.

# Server Setup

First of all, purchase a Ubuntu 20.04 Server from your favorite host. I always use Hetzner for my Projects. Their servers start at 2,96€ per Month for 1vCPU, 2GB of RAM and a 20GB local SSD.[You can use my referral link if you want to check it out too](https://hetzner.cloud/?ref=v1n8s5nlgH4O).

## SSH Config

I always make sure to select my public key when I create the server so that one is already entered in the `~/.ssh/authorized_keys`file for the root user.

If you provide an SSH key, Hetzner will automatically disable Password authentication which provides an extra layer of security.

Make sure your `/etc/ssh/sshd_config` file does not allow password authentication (`#PasswordAuthentication yes` see the hash at the start of the line).

If you would need to change that, make sure to restart the ssh daemon afterwards (`systemctl restart sshd`).

## Set the A record

Once you purchased your server, you will receive a IPv4 Address. When you already purchased a domain for your project, make sure to point the A Record of that domain to your newly purchased Server’s IPv4 Address. I always do that before setting up my server because that might take a few minutes to propagate through the DNS Servers.

This A record needs to be fully propagated once we try to aquire LetsEncrypt certificates later on.

## Update packages

Run

```
apt-get update

```

to update your packages.

## Firewall setup

Install ufw (uncomplicated firewall) and allow ssh, http and https.

```
apt-get install ufw
ufw status
ufw allow ssh
ufw allow http
ufw allow https
ufw enable
ufw status

```

## Create the rails user

Let’s create a new user called `rails` just for running the application.

```
adduser rails

```

This will create an interactive input for creating the user, set a password and just confirm the rest (ENTER).

We want to be able to ssh into our rails user. Lets copy our `~/.ssh/authorized_keys` file from the root user.

```
mkdir -p /home/rails/.ssh
cp ~/.ssh/authorized_keys /home/rails/.ssh
chown -R rails:rails /home/rails/.ssh/

```

This user needs to be a sudo user in order to restart the puma systemctl service we are going to create later on.

```
usermod -aG sudo rails
vi /etc/sudoers
# add the following line at the bottom
# rails ALL=(ALL) NOPASSWD: ALL

```

You should be able to ssh into your server with the rails user now.

```
ssh rails@mysite.com

```

## Install postgres

```
apt-get install postgresql postgresql-contrib libpq-dev
su postgres
cd
createdb mysite_production
psql
create user rails with password 'mypassword';
grant all privileges on database mysite_production to rails;
exit # exit psql shell
exit # back to root user

```

## Install more dependencies

Next we need to install more dependencies

- Node
- Yarn
- RVM
- NGINX

### Node & Yarn

```
# see https://github.com/nodesource/distributions/blob/master/README.md
curl -fsSL https://deb.nodesource.com/setup_14.x | sudo -E bash -
apt-get install -y nodejs
node -v
npm install --global yarn
yarn -v

```

### RVM

```
su rails
cd
# see https://rvm.io/rvm/install
gpg --keyserver hkp://pool.sks-keyservers.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
curl -sSL https://get.rvm.io | bash -s stable --ruby

```

Check on your local machine which ruby version is used in your project (3.0.1 for me)

```
cd mysite
cat Gemfile | grep ruby

```

Then install that same version on the server using RVM.

```
# as root
/home/rails/.rvm/bin/rvm install ruby-3.0.1
su rails
cd
source ~/.rvm/scripts/rvm
rvm use ruby-3.0.1
rvm use --default ruby-3.0.1
rvm -v
ruby -v

```

### Install nginx

```
apt-get install nginx
systemctl start nginx
systemctl enable nginx

```

### Install nginx

```
apt-get install git

```

# Install capistrano

Add the following gems to your `group :development do` block inside the `Gemfile`.

```
gem 'capistrano', require: false
gem 'capistrano-rvm', require: false
gem 'capistrano-rails', require: false
gem 'capistrano-bundler', require: false
gem 'capistrano3-puma', require: false

```

Install the gems and install cap.

```
bundle install
cap install

```

Add the following to your `Capfile`

```
...
require "capistrano/rails"
require "capistrano/bundler"
require "capistrano/rvm"
require 'capistrano/puma'
install_plugin Capistrano::Puma
install_plugin Capistrano::Puma::Systemd
...

```

Adjust your `config/deploy.rb` file to look like this

```
lock "~> 3.16.0"

# replace obvious parts
server 'mysite.com', port: 22, roles: [:web, :app, :db], primary: true
set :application, "mysite"
set :repo_url, "git@github.com:me/mysite.git"

set :user, 'rails'
set :puma_threads, [4, 16]
set :puma_workers, 0

set :pty, true
set :use_sudo, false
set :stage, :production
set :deploy_via, :remote_cache
set :deploy_to, "/home/#{fetch(:user)}/apps/#{fetch(:application)}"
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.access.log"
set :puma_error_log, "#{release_path}/log/puma.error.log"
set :ssh_options, { forward_agent: true, user: fetch(:user), keys: %w(~/.ssh/id_rsa.pub) }
set :puma_preload_app, true
set :puma_worker_timeout, nil
set :puma_init_active_record, true # Change to false when not using ActiveRecord

append :linked_files, "config/master.key"
append :linked_dirs, "log", "tmp/pids", "tmp/cache", "public/uploads"

```

Commit and push your changes.

Copy your `master.key` to the shared dir.

```
ssh rails@mysite.com
mkdir -p apps/mysite/shared/config
# back on your machine
cd mysite
scp config/master.key rails@mysite.com:apps/mysite/shared/config

```

Adjust your `config/database.yml` file.

```
production:
  <<: *default
  database: mysite_production
  host: localhost
  username: rails
  password: mypassword

```

Test a production deploy

```
cap production deploy

```

If you are on mac os, you might encounter this error.

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

In that case just run

```
bundle lock --add-platform x86_64-linux
# commit & push

```

# Puma systemd service

The deploy should fail in the end with the message `Failed to restart puma_mysite_production.service: Unit puma_mysite_production.service not found.`

So let’s create this service.

```
vi /etc/systemd/system/puma_mysite_production.service

```

Enter the following

```
[Unit]
Description=Puma HTTP Server for mysite (production)
After=network.target

[Service]
Type=simple
User=rails
WorkingDirectory=/home/rails/apps/mysite/current
ExecStart=/home/rails/.rvm/bin/rvm default do bundle exec puma -C /home/rails/apps/mysite/shared/puma.rb
ExecReload=/bin/kill -TSTP $MAINPID
StandardOutput=append:/home/rails/apps/mysite/current/log/puma.access.log
StandardError=append:/home/rails/apps/mysite/current/log/puma.error.log
Restart=always
RestartSec=1
SyslogIdentifier=puma

[Install]
WantedBy=multi-user.target

```

Create the directory for the puma sockets to live in:

```
mkdir apps/mysite/shared/tmp/sockets

```

You should be able to run the service now.

```
systemctl start puma_mysite_production.service
systemctl enable puma_mysite_production.service
systemctl status puma_mysite_production.service

```

Try to deploy again, this time it should work fine.

```
cap production deploy

```

# NGINX Setup

We need a webserver to proxy httpp and httpps requests to puma. NGINX does this nicely.

```
vi /etc/nginx/sites-enabled/mysite

```

Add the following

```
upstream puma {
  server unix:///home/rails/apps/mysite/shared/tmp/sockets/mysite-puma.sock;
}

server {
  server_name mysite.com;

  root /home/rails/apps/mysite/current/public;
  access_log /home/rails/apps/mysite/current/log/nginx.access.log;
  error_log /home/rails/apps/mysite/current/log/nginx.error.log info;

  location ^~ /assets/ {
    gzip_static on;
    expires max;
    add_header Cache-Control public;
  }

  try_files $uri/index.html $uri @puma;
  location @puma {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-Ssl on; # Optional
    proxy_set_header X-Forwarded-Port $server_port;
    proxy_set_header X-Forwarded-Host $host;

    proxy_redirect off;

    proxy_pass http://puma;
  }

  error_page 500 502 503 504 /500.html;
  client_max_body_size 100M;
  keepalive_timeout 10;
}

```

Check if the config is valid & restart nginx.

```
nginx -t
systemctl restart nginx

```

In case you get a permissions error, running the following should fix that:

```
sudo chown rails:rails -R apps/mysite/

```

# Adding LetsEncrypt

You should be able to access your page now via http.

You need SSL Certificates in order to run your site via https. LetsEncrypt is free and easy to setup with nginx.

```
apt-get install certbot python3-certbot-nginx
certbot --nginx

```

Follow the interactive installer, I always choose redirect in the end and you should probably too. Refresh your page, you should be redirect to https now.

# Debugging

Errors can occurr in a few places here are a few hints:

```
# check if nginx is running
systemctl status nginx
journalctl -u nginx

# check if puma is running
systemctl status puma_mysite_production.service
journalctl -u puma_mysite_production.service

# application logs
tail -f apps/mysite/current/log/*

```

Hope you find it useful. Have a nice day ;)