When i started making my first ruby on rails app, it worked great on local development environment. The idea was simple enough, the app would generate the CV for the user and also let them download it. So, i thought, why not deploy in production server?

There are places where we can deploy your ruby on rails app for free such as heroku. I will be discussing the place where i deployed my first app: “DigitalOcean.”

DigitalOcean

DigitalOcean is one of the favorite and cheap place to deploy your rails app. You can either create a VM and either completely configure from scratch or you can create machine specially for the targeted purpose. For this purpose i didn’t go through scratch process because i am familiar with the setup process for nginx and puma server so i chose “One-Click-Apps”. One click apps process lets you setup instantly. The available apps are Ruby on Rails, Wordpress, Discourse, phpmyAdmin etc.

Setup Process

1. To configure your VM, click on Ruby on Rails on 16.04.

2. The standard droplet, will be 1 GB. 1vCPU, 25GB SSD which cost $5/month which fits for our need.

3. You can choose any datacenter nearest to your location.

4. Setup additional options and setup your SSH keys.

5. Finally choose name for your droplet and click on create option.

This will finally create the VM and provide you the IP address from which can access using SSH.

Once the machine is created, we can access using SSH.

Goto terminal and type ssh root@IP.

This takes us right to the machine and we can check the ruby, rails and other needed things.

Please make sure you can configure and install the following.

1. GIT Configuration

2. SSH Configuration

3. Postgresql Configuration

4. Nginx

5. System Upgrade

For this instance, i configured my SSH keys to github. You can configure this to bitbucket as well.

Once the SSH keys are configured, clone your app to the VM, to check if our setup is working. You can do this by

$ git clone [email protected]:username/name-of-the-app.git

There is no need to keep this repo as we will use capistrano for our cloning and other purpose.

Our real work now.

After finishing all the stuff, its time to setup our app. We are going to use capistrano..

Add this to your Gemfile.

1
2
3
4
5
6
7
8
9
    group :development do

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

    end

Run $ bundle install to install gems now.

This does

1. Creates Capfile in the project’s root path.

2. deploy.rb in the config directory.

3. deploy folder in the config directory.

After the gem is installed, now it is time to configure capistrano. Run the following command to configure capistrano.

$ cap install

Replace the content of the newly generated capfile like this.

1
2
3
4
5
6
7
8
9
10
11
12
13
    # Load DSL And Setup Up Stages
    Require 'Capistrano/Setup'
    Require 'Capistrano/Deploy'

    Require 'Capistrano/Rails'
    Require 'Capistrano/Bundler'
    Require 'Capistrano/Rvm'
    Require 'Capistrano/Puma'

    # Loads Custom Tasks From `Lib/Capistrano/Tasks' If You Have Any Defined.
    Dir.Glob('Lib/Capistrano/Tasks/*.Rake').Each { |R| Import R }

  

Now the main task is to configure the deploy.rb which does the automation part. Replace the deploy.rb with the following.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# Change these
server 'IP', port: 'port', roles: [:web, :app, :db], primary: true

set :repo_url,        'repo location'
set :application,     'app name'
set :user,            'user name in the VM'
set :puma_threads,    [4, 16]
set :puma_workers,    0

# Don't change these unless you know what you're doing
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.error.log"
set :puma_error_log,  "#{release_path}/log/puma.access.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

## Defaults:
# set :scm,           :git
# set :branch,        :master
# set :format,        :pretty
# set :log_level,     :debug
# set :keep_releases, 5

## Linked Files & Directories (Default None):
# set :linked_files, %w{config/database.yml}
# set :linked_dirs,  %w{bin log tmp/pids tmp/cache tmp/sockets vendor/bundle public/system}

namespace :puma do
  desc 'Create Directories for Puma Pids and Socket'
  task :make_dirs do
    on roles(:app) do
      execute "mkdir #{shared_path}/tmp/sockets -p"
      execute "mkdir #{shared_path}/tmp/pids -p"
    end
  end

  before :start, :make_dirs
end

namespace :deploy do
  desc "Make sure local git is in sync with remote."
  task :check_revision do
    on roles(:app) do
      unless `git rev-parse HEAD` == `git rev-parse origin/master`
        puts "WARNING: HEAD is not the same as origin/master"
        puts "Run `git push` to sync changes."
        exit
      end
    end
  end

  desc 'Initial Deploy'
  task :initial do
    on roles(:app) do
      before 'deploy:restart', 'puma:start'
      invoke 'deploy'
    end
  end

  desc 'Restart application'
  task :restart do
    on roles(:app), in: :sequence, wait: 5 do
      invoke 'puma:restart'
    end
  end

  before :starting,     :check_revision
  after  :finishing,    :compile_assets
  after  :finishing,    :cleanup
  after  :finishing,    :restart
end

# ps aux | grep puma    # Get puma pid
# kill -s SIGUSR2 pid   # Restart puma
# kill -s SIGTERM pid   # Stop puma

The main part to configure or replace are

1. Server IP – Provided by DigitalOcean

2. Port – 22

3. repo_url – your github/bitbucket url{depends on what you setup earlier for SSH}

4. application – app name

5. user – username in the VM

Once all this finishes, it is time to setup our nginx. Create a file nginx.conf inside config directory and add the following code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
upstream puma {
  server unix:///home/deploy/apps/appname/shared/tmp/sockets/appname-puma.sock;
}

server {
  listen 80 default_server deferred;
  # server_name example.com;

  root /home/deploy/apps/appname/current/public;
  access_log /home/deploy/apps/appname/current/log/nginx.access.log;
  error_log /home/deploy/apps/appname/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_redirect off;

    proxy_pass http://puma;
  }

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

Remember you need to change the username from deploy to your username and appname to whatever you wrote in the deploy.rb.

Push all the changes in the git server and run the deployment as

$ cap production deploy:inital

This will take time depending upon your app and internet connection. Once the process finishes, connect your Puma web server to the Nginx reverse proxy.

$ sudo rm /etc/nginx/sites-enabled/default
$ sudo ln -nfs "/home/deploy/apps/appname/current/config/nginx.conf" "/etc/nginx/sites-enabled/appname"

Again remember to replace the app name and username in above lines.

Restart out the nginx service and Boom we have our app ready. Go with your IP address in the browser and TADA its done!!

This tutorial was inspired by deploying your ruby on rails app using nginx, puma web server in DigitalOcean VM.