As Ruby on Rails developers we get used to hosting our apps on platforms such as Heroku, which make configuration and deployment fairly easy. Sometimes, however, we want to have more control over server configuration and all of the steps of deployment, backup or monitoring. VPS providers like AWS EC2 or DigitalOcean droplets come in handy in such cases. Maintaining those may be not as simple as in case of Heroku, but there are few automation tools written in ruby (e.g. Capistrano, Chef) that make this task easier.

This article describes basic steps to set up deployment-ready VPS instance using Capistrano with RVM, Puma as application server and Nginx as reverse proxy. Let’s assume we already have working rails application, hosted on remote git repository. For sake of this article I’ve chosen “t2.micro” EC2 instance with “Ubuntu Server 14.10 LTS”. Let’s set it up.

Set up the server

At this point you should know IP address of the server and have private key stored on your local machine. It’s a file generated on creation of the instance and required to connect to it for the first time. Log into the server using SSH:

$ ssh -i "keypair.pem" ubuntu@XXX.XXX.XXX.XXX

Update existing packages on your new server:

$ sudo apt-get update && sudo apt-get -y upgrade

Now, in order to use capistrano to deploy your app to this server you need to do some preparation. First, create user named deploy, with its home directory and set password for it.

$ sudo useradd -d /home/deploy -m deploy
$ sudo passwd deploy

Add deploy to sudo privileged user by rinning sudo visudo. Paste following line to opened file and save.

deploy ALL=(ALL:ALL) ALL

The deploy will need to access your git repository for deployment. Generate a key pair for him then, but do not set the passphrase:

$ su - deploy
$ ssh-keygen

Add newly generated public key (.ssh/id_rsa.pub) to your repository’s deployment keys.

Capistrano uses deploy account for deployment and connects to it by SSH. To make this possible, copy public key from your local machine and add it to authorized keys of deploy user. On server, edit this file:

$ vim .ssh/authorized_keys

Finally, install Git which is required for automated deployment with Capistrano:

$ sudo apt-get install git

Nginx

We will use Nginx as reverse proxy. It will redirect HTTP requests to the Puma application server through a UNIX socket. Let’s install it:

$ sudo apt-get install nginx

and configure default site. Open the site config file:

$ sudo vim /etc/nginx/sites-available/default

comment out existing content and paste the following into the file:

upstream app {
  # Path to Puma SOCK file, as defined previously
  server unix:/home/deploy/my-app/shared/tmp/sockets/puma.sock fail_timeout=0;
}

server {
        listen 80;
        server_name localhost;

        root /home/deploy/my-app/current/public;

        try_files $uri/index.html $uri @app;

        location / {
                proxy_set_header X-Forwarded-Proto $scheme;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header Host $host;
                proxy_redirect off;
                proxy_http_version 1.1;
                proxy_set_header Connection '';
                proxy_pass http://app;
        }

        location ~ ^/(assets|fonts|system)/|favicon.ico|robots.txt {
          gzip_static on;
          expires max;
          add_header Cache-Control public;
        }

        error_page 500 502 503 504 /500.html;
        client_max_body_size 4G;
        keepalive_timeout 10;
}

Set up the database

You need to set up a database for your app. If you are using PostgreSQL, here are steps:

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

Create database user (‘myapp’ in this example):

$ sudo -u postgres createuser -s myapp

Log into PostgreSQL console:

$ sudo -u postgres psql

And change the password for newly created user:

postgres=# \password myapp

Confirm and exit console with \q. Next, create the database:

$ sudo -u postgres createdb -O myapp my-app_production

RVM and Ruby

If you are not, login again to deploy user’s shell. Run following lines to install RVM:

$ gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
$ \curl -sSL https://get.rvm.io | bash -s stable

This will install RVM into deploy user’s home directory. Logout and log in again to load into the shell. Now, install ruby using rvm.

You can install latest stable version:

$ rvm install ruby

or specific one:

$ rvm install 2.2.3

After it’s installed switch to installed version:

$ rvm use 2.2.3

Also install bundler gem:

$ gem install bundler --no-ri --no-rdoc

Capistrano prerequisites

Before first deployment we have to create directories and files to store database settings and environment specific data that are required by Capistrano.

$ mkdir my-app
$ mkdir -p my-app/shared/config
$ vim my-app/shared/config/database.yml

Paste the following in database.yml and save:

production:
  adapter: postgresql
  encoding: unicode
  database: my-app_production
  username: myapp
  password: mypassword
  host: localhost
  port: 5432

Next, create application.yml

$ vim my-app/shared/config/application.yml

and add the following line:

SECRET_KEY_BASE: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

But first go to your local machine and run the rake secret command to generate new secret to replace the one above.

Node.js

One final thing before we finish setting up the server. Rails use Node.js as Javascript runtime to compile assets, so you might need to install it:

$ curl -sL https://deb.nodesource.com/setup_4.x | sudo -E bash -
$ sudo apt-get install -y nodejs

Now, let’s configure app itself. go back to your local machine.

Deploying Rails App with Capistrano

Capistrano lets you configure set of tasks needed to perfom deploymentof your applications to remote servers and run it automatically. First, add following gems to to your Gemfile:

gem 'figaro'
gem 'puma'
group :development do
  gem 'capistrano'
  gem 'capistrano3-puma'
  gem 'capistrano-rails', require: false
  gem 'capistrano-bundler', require: false
  gem 'capistrano-rvm'
end

And install them with bundler:

$ bundle install

Now, to configure capistrano deployments you need to generate proper config files by running following command:

$ cap install STAGES=production

It will create config/deploy.rb file which is main configuration file and config/deploy/production.rb which contains environment specific settings, such as server IP, username, etc.

It also generates Capfile in the root of application, where you require dependencies that you need to accomplish task. Add following lines to Capfile after require 'capistrano/deploy':

require 'capistrano/bundler'
require 'capistrano/rvm'
require 'capistrano/rails/assets' # for asset handling add
require 'capistrano/rails/migrations' # for running migrations
require 'capistrano/puma'

Edit deploy.rb as follows:

lock '3.4.0'

set :application, 'my-app'
set :repo_url, 'git@github.org:mnieborak/my-app.git'  # Edit this to match your repository
set :branch, :master
set :deploy_to, '/home/deploy/my-app'
set :pty, true
set :linked_files, %w{config/database.yml config/application.yml}
set :linked_dirs, %w{bin log tmp/pids tmp/cache tmp/sockets vendor/bundle public/system public/uploads}
set :keep_releases, 5
set :rvm_type, :user
set :rvm_ruby_version, 'ruby-2.2.3'  # Edit this to match ruby version you use

set :puma_rackup, -> { File.join(current_path, 'config.ru') }
set :puma_state, "#{shared_path}/tmp/pids/puma.state"
set :puma_pid, "#{shared_path}/tmp/pids/puma.pid"
set :puma_bind, "unix://#{shared_path}/tmp/sockets/puma.sock"    #accept array for multi-bind
set :puma_conf, "#{shared_path}/puma.rb"
set :puma_access_log, "#{shared_path}/log/puma_error.log"
set :puma_error_log, "#{shared_path}/log/puma_access.log"
set :puma_role, :app
set :puma_env, fetch(:rack_env, fetch(:rails_env, 'production'))
set :puma_threads, [0, 8]
set :puma_workers, 0
set :puma_worker_timeout, nil
set :puma_init_active_record, true
set :puma_preload_app, false

Of course change my-app to name of your app.

To set the server IP open config/deploy/production.rb and paste the following line. Change IP address to your server’s IP:

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

As you might notice in Gemfile, we are also using Figaro gem. It lets you set environment-specific configuration using single YAML file. You can run $ figaro install which creates config/application.yml and adds it to .gitignore or you can do it by hand.

Everything is ready to start first deployment using Capistrano:

$ cap production deploy

On the first deployment Capistrano will create hierarchy of directories and files that it uses to organise the source code and other deployment-related data. Then it will deploy the app, migrate database and start Puma server. Finally login to your server again and restart nginx service to reload new configuration:

$ sudo service nginx restart

Now, you can visit your app in browser using your server’s IP address. You should see your application up and running.

Summary

Now, you should know basic “how to” of deploying Rails application to VPS using Capistrano and Nginx. But as your app grows you can use Capistrano configuration and tasks to accomplish much more. I encourage you to further reading, as there are many more subjects concerning deployment and maintenace of Rails apps on VPSes.

Thank you, and looking forward for comments :)

Post by Marcin Nieborak

Marcin joined our team in 2013. He specializes in Ruby, JavaScript and Elixir.