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
- Ubuntu
- Ruby
- Rails
- Puma
- Nginx reverse proxy
- Node.js
- Certbot (for your LetsEncrypt certificate setup and management)
- and a rails.service in /etc/systemd/system
- ufw firewall service
- a very helpful message of the day that describes passwords for postgres, sftp
- a deploy user “rails” that also vendors gems
- so to access rails console/logs/update gems/launch puma - you need to “sudo -i -u rails”
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 nginx server block sites-available/your-site-domain-name
- make sure the server name is set there
- make sure it is pointed to in sites-enabled
- remember: ln -s source target_new_name
- then can ask certbot to install a certificate for you
- setup an automated renewal for your sites
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)