Yesterday I spent a little time getting back up to speed on deploying a Rails 6 application to a Digital Ocean Rails Droplet using Capistrano.

1. Create your digital ocean droplet

You can create your droplet using their one click installer or their api. Make sure you use your ssh key to allow your access since Capistrano depends on ssh access (and ssh agent forwarding).

This will setup

2. Setup a domain and LetsEncrypt

(Note: you don’t have to do this step, you can deploy with capistrano using just an IP, but you cannot use https that way (as far as I know)).

By default, the droplet comes with an example rails application that nginx points to as a reverse proxy. This is locate in “/home/rails/example”. Initially this is just running in “development” mode so by browsing to your IP you should see the “Yay! You’re on Rails” view.

Point your domain/subdomain to your droplet ip

Add an “A” record for your domain or subdomain to point to your droplet IP. This can take up to an hour to propagate.

If you don’t change anything, then it should point to your IP but with the new Rails DNS rebinding protection you would just see a “Blocked host” message (but at least you know it is pointing to the right server).

Setup an enabled site block in Nginx for your site

Key steps:

Create an Nginx server block for your site

As root

vim /etc/nginx/sites-available/your-site-domain-name

As you can see this was automatically modified by Certbot, and does the automatic http->https redirection.

Enable this site

Certbot looks in sites-enabled, so make sure you create a symbolic link there.

As root

ln -s /etc/nginx/sites-available/your-site-domain-name /etc/nginx/sites-enabled/your-site-domain-name

At this point I remove the default site from sites-enabled (rm /etc/nginx/sites-enabled/rails).

Install and run certbot

On Ubuntu 20.04, the latest guidance is here: https://certbot.eff.org/lets-encrypt/ubuntufocal-nginx

# Snap is already installed in 20.04
snap --version
# Refresh it
sudo snap install core; sudo snap refresh core
# Certbot is installed by default, but is an older version
certbot --version
sudo apt-get remove certbot
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
certbot --version
# Make sure port 443 is accessible
sudo ufw status
# Let certbot update your site
sudo certbot --nginx -d app.maketimeflow.com
# Confirm the changes
vim app.maketimeflow.com
# Check renewal
sudo certbot renew --dry-run
# It is automatically set to renew as a system timer
systemctl list-timers

On Ubuntu 18.04

sudo add-apt-repository ppa:certbot/certbot
sudo apt-get update
sudo apt-get install python-certbot-nginx

Make sure that port 443 is open (for nginx)

sudo ufw status
sudo certbot --nginx -d your.site.name
Make sure you setup a renewal

Certbot can do automated renewals. This post described a nice crontab that will do the renewal for you. If your site is not due for a renewal, Certbot will not attempt renewal (which is great because you are limited to a certain number of renewals each period).

As root

sudo crontab -e 
30 2 * * 1 /usr/bin/certbot renew 

If there is an error when you first install your certficate, you can have Certbot reinstall it for you

I found this certbot community post helpful, when certbot could not initially find my server block.

certbot --reinstall -d your.site.domain-name

3. Create your Rails app and setup Capistrano to deploy it

Update: The server is setup with Ruby 2.7.2, so use this for local development.

rbenv install 2.7.2
rbenv local 2.7.2
ruby --version
gem install bundler
gem install rails
bundle install --binstubs .bundle/bin
rbenv rehash

Create a Rails app with postgresql db

rails new app-name --database postgresql
bin/setup

Setup your database config and credentials

Then update your master.key with these credentials.

EDITOR=vim rails credentials:edit

Setup Capistrano

bundle install
bundle exec cap install

### Update your Capfile

<script src="https://gist.github.com/56b858aa8c5dedfdea9336e79511be04.js?file=Capfile"> </script>

### Update config/deploy.rb

Note: this has a basic setup for your server, but also code at the bottom that will upload your shared config files if they don’t exist on the server.

Update config/deploy/production.rb

Add Capistrano tasks

This task will let you quickly tail log files on the server.

This task is needed to properly restart the puma service after a deploy.

Important: in order for this to work you need to make this sudo command not require a password.

As root

sudo visudo -f /etc/sudoers.d/rails
rails ALL=NOPASSWD: /bin/systemctl restart rails.service

This was a helpful reference. Note: you are only allowing that single command to be run by that user as sudo without a password, so it is not making all sudo commands not require a password.

4. Deploy

Make sure your ssh agent is setup

On your mac, ssh agent should already be setup. Check to see that the agent has your identity loaded.

ssh-add -l

If not, then just enter ssh-add and your default identity should be loaded.

Do a partial deploy to be able to setup the database

You should now be ready to deploy. There is still a little work since your database has not been setup.

cap production deploy --trace

Note: this failed on a newer version of the droplet since it looks like it has rvm installed, but it does not have a link to it in the expected spot. So you might see an error like rvm stderr: bash: /home/rails/.rvm/bin/rvm: No such file or directory. To fix this add a link in the home dir on your server:

ln -s /usr/share/rvm .rvm

Run again.

This will break because the DB is not setup, but the code is there now with the credentials to do the setup.

As rails

cd app-name/releases

Go into the latest release folder

RAILS_ENV=production bundle exec rails db:setup

This should setup the db.

Do a complete deployment

Now rerun your deployment and it should work.

cap production deploy --trace

5. Update and restart your services

You need to update rails.service to use production and point to your app. As root:

sudo vim /etc/systemd/system/rails.service

Make sure the WorkingDirectory and ExecStart.

WorkingDirectory=/home/rails/appname/current
ExecStart=/bin/bash -lc 'RAILS_ENV=production bundle exec puma'

Reload and restart the services.

systemctl daemon-reload
systemctl restart rails.service
journalctl -u rails.service -f

Appendix/FAQ

Where are logs stored?

Rails logs

Once deployed with Capistrano, these will be stored in /home/rails/#{app}/shared/logs. The “rails” user has access to this. Later we will have a capistrano task to allow you to quickly tail these.

Nginx

As root:

less /var/log/nginx/error.log

Rails.service / Puma logs

As root or rails

journalctl -u rails.service

Add -f to follow.

journalctl -u rails.service -f

How to access Postgres as the postgres user

(as root)

su - postgres
psql
\du (lists users)
\d (lists tables, roles etc)