Deploying a Rails app to Digital Ocean with Capistrano

drbragg

Drew Bragg

Posted on October 4, 2018

Deploying a Rails app to Digital Ocean with Capistrano

This is the basic steps on how I setup my DO servers and deploy my rails apps. I originally learned this from the GoRails screencast but have been iterating over it and tweaking/improving it over time. Maybe this write up will help you get your apps live or maybe you have a better way you can share with me.

If you don't have a Digital Ocean account, you can sign up using this link, https://m.do.co/c/33c1f4fb065a. Yes I get a referral credit if you use my link but only if you end up spending money with them, even if you dont you still get $100 in credits for 60 days if you sign up during Hacktoberfest.

Lets spin up a server!

Creating a droplet

  • Create a droplet

    • Choose Ubuntu as your distribution (16.04.4 x64)
    • Choose the size of your droplet (min 1GB and 1CPU)
    • Choose New York 1 or 3 for your datacenter (I'm on the East Coast so this makes the most sense for me)
    • Optional:
    • Add SSH keys now (can also do this later)
    • Rename the hostname (generally to something that makes sense)
  • Once the droplet is created an email is sent with the login credentials

  • Use the provided credentials to SSH into the new droplet

  • It is best practice to make a new user to manage deployments rather that using root

    • $ sudo adduser deploy
    • $ sudo adduser deploy sudo
    • $ su deploy

If you did not provide SSH keys when you created the droplet you should do that now. If you did, skip this:

Do this on your computer NOT on the server!

I use a MacOS tool called ssh-copy-id. If you don't have it you can get it by running $ brew install ssh-copy-id
* Run $ ssh-copy-id deploy@SERVERIP where SERVERIP is the IP address of your droplet

Back to our regularly scheduled program

  • SSH back into the server as the deploy user

Run the following on the server as deploy

  • First get Node and Yarn.

    • $ curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
    • $ 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
  • Then install you basic dependencies.

    • $ sudo apt-get update
    • $ 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 nodejs yarn
  • Then install rbenv (for managing our ruby versions, you can use rvm if you want but I'm partial to rbenv)

    • $ cd
    • $ git clone https://github.com/rbenv/rbenv.git ~/.rbenv
    • $ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
    • $ echo 'eval "$(rbenv init -)"' >> ~/.bashrc
    • $ exec $SHELL
    • $ git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
    • $ echo 'export PATH="$HOME/.rbenv/plugins/ruby-build/bin:$PATH"' >> ~/.bashrc
    • $ exec $SHELL
  • Finally install ruby

    • $ rbenv install 2.4.1 -- or whatever version of ruby your application is running
    • $ rbenv global 2.4.1 -- or whatever you just installed
    • $ ruby -v -- to verify the correct version is installed and set
    • $ gem install bundler
    • $ rbenv rehash

Setup Nginx with Passenger

This will actually run our 'server' and serve (no pun intended) our app

  • $ sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 561F9B9CAC40B2F7
  • $ sudo apt-get install -y apt-transport-https ca-certificates

  • $ sudo sh -c 'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger xenial main > /etc/apt/sources.list.d/passenger.list'

  • $ sudo apt-get update

  • $ sudo apt-get install -y nginx-extras passenger

Check to make sure everything you just did worked correctly

Run $ sudo service nginx start on the server and then visit the droplets IP address in the browser

If you did everything right so far you should see an Nginx Welcome message.

Configure Nginx and Passenger
  • $ sudo vim /etc/nginx/nginx.conf
    • Find the following lines of code and uncomment include /etc/nginx/passenger.conf:
      ##
      # Phusion Passenger
      ##
      # Uncomment it if you installed ruby-passenger or ruby-passenger-enterprise
      ##

include /etc/nginx/passenger.conf;

  • Save and close (:wq)

    • $ sudo vim /etc/nginx/passenger.conf
  • Change the passenger_ruby line to point to rbenv (or rvm if you used that):

    • passenger_ruby /home/deploy/.rbenv/shims/ruby;
  • Save and close (:wq)

Once this is all completed restart the Nginx Server
  • $ sudo service nginx restart

Environmental Variables

  • Ensure you are in the top most level of the deploy user section
    • Run $ cd ~
  • $ git clone https://github.com/rbenv/rbenv-vars.git $(rbenv root)/plugins/rbenv-vars
  • $ vim .rbenv-vars
  • Add any Environmental Variables you need to the file.

PostgreSQL

  • Run $ sudo apt-get install postgresql postgresql-contrib libpq-dev
  • Set up the postgres user (also named "deploy" but different from our linux user named "deploy") and DB
    • $ sudo su - postgres
    • $ createuser --pwprompt deploy -- make note of the password you choose
    • $ createdb -O deploy APP_NAME_production -- where APP_NAME is the name of the application
    • $ exit

Deploying

First we need to configure our repo's bucket to be able to talk to our server. Since we deploy directly from the repo rather that our local machine.

  • While SSHed in our server, enter ssh-keygen at the command line.
  • Follow the prompts to set up a new key. A passphase is not needed in this case.
  • Once the server has generated a new key run cat ~/.ssh/id_rsa.pub and copy the returned key
  • Add the key to the App's repo
    • If you're using bitbucket that can be found in settings > access keys

Back on your local machine let's set up capistrano

  • In your Gemfile add:

    gem 'capistrano'
    gem 'capistrano-rails'
    gem 'capistrano-passenger'
    gem 'capistrano-rbenv' (if you used rvm use: gem 'capistrano-rvm')

  • Run $ bundle install and then $ cap install

If you want to customize this install consult the capistrano docs.

  • In config/deploy.rb change the following lines, updating them to the name and repo of the current application:

    set :application, "YOUR_APPS_NAME"
    set :repo_url, "YOUR_APPS_REPO"
    
    set :deploy_to, "/home/deploy/YOUR_APPS_NAME"
    
    append :linked_files, "config/database.yml", "config/secrets.yml"
    
    append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "public/system"
    
  • In config/deploy/production.rb change the IP address of the server to the IP of the new droplet

    server '104.131.189.221', user: 'deploy', roles: %w{app db web}
    
  • You can then use the command cap production deploy to deploy new versions of the app

    • The first time you try this it will fail and throw an error, but we're almost there.

Home Stretch

SSH back into the server as deploy and create 2 files

  • /home/deploy/APP_NAME/shared/config/database.yml

    • In this file add the following:
      production:
      adapter: postgresql
      host: 127.0.0.1
      database: APP_NAME_production
      username: deploy
      password: YOUR_POSTGRES_PASSWORD (NOT YOUR LINUX PASSWORD)
      encoding: unicode
      pool: 5
      
  • /home/deploy/APP_NAME/shared/config/secrets.yml

    • In this file add the following:
      production:
      secret_key_base: YOUR_SECRET_KEY
      
      (Note: I'm fairly certain there is a better way to do this in Rails 5.2 but I haven't personally used that in production yet)

Run $ sudo visudo -f /etc/sudoers.d/nginx_overrides and add the following to the file:

  # Nginx Commands
  Cmnd_Alias NGINX_RESTART = /usr/sbin/service nginx restart
  Cmnd_Alias NGINX_RELOAD  = /usr/sbin/service nginx reload

  # No-Password Commands
  deploy ALL=NOPASSWD: NGINX_RESTART, NGINX_RELOAD

Updating the URL

Last this to do on the server is to tell Nginx to respond with the app

  • Run sudo ln -s /etc/nginx/sites-available/THE_URL_YOU_WANT_HERE.com /etc/nginx/sites-enabled/
  • Then sudo vim /etc/nginx/sites-enabled/THE_URL_YOU_WANT_HERE.com and replace the files contents with:

    server {
      listen 80;
      listen [::]:80 ipv6only=on;
    
      server_name THE_URL_YOU_WANT_HERE.com;
      passenger_enabled on;
      rails_env    production;
      root         /home/deploy/APP_NAME/current/public;
    
      # redirect server error pages to the static page /50x.html
      error_page   500 502 503 504  /50x.html;
      location = /50x.html {
          root   html;
      }
    }
    
  • Back on your local machine run $ cap production deploy once more and then you should be in business

  • If you are still seeing the default Nginx page, run $ cap production server:restart

If I missed anything or there's something I did wrong (or could do better) please let me know. Obviously there's a lot more to running a 'real' production server, logs, security, load balancing, etc, but this will get your app out into the world. Plus it feels pretty cool to configure your own server.

💖 💪 🙅 🚩
drbragg
Drew Bragg

Posted on October 4, 2018

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related