yz8cha
Last Updated: November 24, 2016
·
70.89K
· jamesdullaghan
Photo on 6 5 13 at 8.07 pm

Deploying Rails app using Nginx, Unicorn, Postgres and Capistrano to Digital Ocean

Warning: This guide is out of date. Please visit gorails.com for a more up-to-date, simple deployment walkthrough or one of digital oceans many how-to's.

Create droplet of your liking (ubuntu 12.10 x32)

ssh to root in terminal with your server ip

ssh root@123.123.123.123

Add ssh fingerprint and enter password provided
Change password

passwd

Create new user

adduser username

Set new users privileges

visudo

Find user privileges section

# User privilege specification
root  ALL=(ALL:ALL) ALL

Add your new user privileges under root & cntrl+x then y to save

username ALL=(ALL:ALL) ALL

Configure SSH

nano /etc/ssh/sshd_config

Find and change port to one that isn't default(22 is default: choose between 1025..65536)

Port 22 # change this to whatever port you wish to use
Protocol 2
PermitRootLogin no

Add to bottom of sshd_config file after changing port (cntrl+x then y to save)

UseDNS no
AllowUsers username

Reload ssh

reload ssh

Don't close root! Open new shell and ssh to vps with new username(remember the port, or you're locked out!)

ssh -p 1026 username@123.123.123.123

Update packages on virtual server

sudo apt-get update
sudo apt-get install curl

install latest stable version of rvm

curl -L get.rvm.io | bash -s stable

load rvm

source ~/.rvm/scripts/rvm

install rvm dependencies

rvm requirements

Install ruby 2.0.0

rvm install 2.0.0

Use 2.0.0 as rvm default

rvm use 2.0.0 --default

install latest version of rubygems if rvm install didn't

rvm rubygems current

install rails gem

gem install rails --no-ri --no-rdoc

Install postgres

sudo apt-get install postgresql postgresql-server-dev-9.1
gem install pg -- --with-pg-config=/usr/bin/pg_config

Create new postgres user

sudo -u postgres psql
create user username with password 'password';
alter role username superuser createrole createdb replication;
create database projectname_production owner username;

Install git-core

sudo apt-get install git-core

Install bundler

gem install bundler

setup nginx

sudo apt-get install nginx
nginx -h
cat /etc/init.d/nginx
/etc/init.d/nginx -h
sudo service nginx start
cd /etc/nginx

local unicorn setup

Add unicorn to the gemfile
create unicorn.rb & unicorn_init.sh file
chmod +x config/unicorn_init.sh

nginx.conf (change projectname and username to match your directory structure!) (also be aware of client_max_body_size setting, please look at nginx documentation for more information!)

upstream unicorn {
  server unix:/tmp/unicorn.projectname.sock fail_timeout=0;
}

server {
  listen 80 default_server deferred;
  # server_name example.com;
  root /home/username/apps/projectname/current/public;

  location ^~ /assets/ {
    gzip_static on;
    expires max;
    add_header Cache-Control public;
  }

  try_files $uri/index.html $uri @unicorn;
  location @unicorn {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://unicorn;
  }

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

config/unicorn.rb

root = "/home/username/apps/projectname/current"
working_directory root
pid "#{root}/tmp/pids/unicorn.pid"
stderr_path "#{root}/log/unicorn.log"
stdout_path "#{root}/log/unicorn.log"

listen "/tmp/unicorn.projectname.sock"
worker_processes 2
timeout 30

# Force the bundler gemfile environment variable to
# reference the capistrano "current" symlink
before_exec do |_|
  ENV["BUNDLE_GEMFILE"] = File.join(root, 'Gemfile')
end

config/unicorn_init.sh

#!/bin/sh
### BEGIN INIT INFO
# Provides:          unicorn
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Manage unicorn server
# Description:       Start, stop, restart unicorn server for a specific application.
### END INIT INFO
set -e

# Feel free to change any of the following variables for your app:
TIMEOUT=${TIMEOUT-60}
APP_ROOT=/home/username/apps/projectname/current
PID=$APP_ROOT/tmp/pids/unicorn.pid
CMD="cd $APP_ROOT; bundle exec unicorn -D -c $APP_ROOT/config/unicorn.rb -E production"
AS_USER=username
set -u

OLD_PIN="$PID.oldbin"

sig () {
  test -s "$PID" && kill -$1 `cat $PID`
}

oldsig () {
  test -s $OLD_PIN && kill -$1 `cat $OLD_PIN`
}

run () {
  if [ "$(id -un)" = "$AS_USER" ]; then
    eval $1
  else
    su -c "$1" - $AS_USER
  fi
}

case "$1" in
start)
  sig 0 && echo >&2 "Already running" && exit 0
  run "$CMD"
  ;;
stop)
  sig QUIT && exit 0
  echo >&2 "Not running"
  ;;
force-stop)
  sig TERM && exit 0
  echo >&2 "Not running"
  ;;
restart|reload)
  sig HUP && echo reloaded OK && exit 0
  echo >&2 "Couldn't reload, starting '$CMD' instead"
  run "$CMD"
  ;;
upgrade)
  if sig USR2 && sleep 2 && sig 0 && oldsig QUIT
  then
    n=$TIMEOUT
    while test -s $OLD_PIN && test $n -ge 0
    do
      printf '.' && sleep 1 && n=$(( $n - 1 ))
    done
    echo

    if test $n -lt 0 && test -s $OLD_PIN
    then
      echo >&2 "$OLD_PIN still exists after $TIMEOUT seconds"
      exit 1
    fi
    exit 0
  fi
  echo >&2 "Couldn't upgrade, starting '$CMD' instead"
  run "$CMD"
  ;;
reopen-logs)
  sig USR1
  ;;
*)
  echo >&2 "Usage: $0 <start|stop|restart|upgrade|force-stop|reopen-logs>"
  exit 1
  ;;
esac

Add capistrano and rvm capistrano to gemfile

gem 'capistrano'
gem 'rvm-capistrano'

Create capfile & config/deploy.rb files

capify .

deploy.rb

require "bundler/capistrano"
require "rvm/capistrano"

server "123.123.123.123", :web, :app, :db, primary: true

set :application, "projectname"
set :user, "username"
set :port, 22
set :deploy_to, "/home/#{user}/apps/#{application}"
set :deploy_via, :remote_cache
set :use_sudo, false

set :scm, "git"
set :repository, "git@github.com:username/#{application}.git"
set :branch, "master"


default_run_options[:pty] = true
ssh_options[:forward_agent] = true

after "deploy", "deploy:cleanup" # keep only the last 5 releases

namespace :deploy do
  %w[start stop restart].each do |command|
    desc "#{command} unicorn server"
    task command, roles: :app, except: {no_release: true} do
      run "/etc/init.d/unicorn_#{application} #{command}"
    end
  end

  task :setup_config, roles: :app do
    sudo "ln -nfs #{current_path}/config/nginx.conf /etc/nginx/sites-enabled/#{application}"
    sudo "ln -nfs #{current_path}/config/unicorn_init.sh /etc/init.d/unicorn_#{application}"
    run "mkdir -p #{shared_path}/config"
    put File.read("config/database.example.yml"), "#{shared_path}/config/database.yml"
    puts "Now edit the config files in #{shared_path}."
  end
  after "deploy:setup", "deploy:setup_config"

  task :symlink_config, roles: :app do
    run "ln -nfs #{shared_path}/config/database.yml #{release_path}/config/database.yml"
  end
  after "deploy:finalize_update", "deploy:symlink_config"

  desc "Make sure local git is in sync with remote."
  task :check_revision, roles: :web 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
  before "deploy", "deploy:check_revision"
end

Capfile

load 'deploy'
load 'deploy/assets'
load 'config/deploy'

Shake hands with github

# follow the steps in this guide if receive permission denied(public key)
# https://help.github.com/articles/error-permission-denied-publickey
ssh github@github.com

Add ssh key to digitalocean

cat ~/.ssh/id_rsa.pub | ssh -p 22 username@123.123.123.123 'cat >> ~/.ssh/authorized_keys'

Create repo and push to github

# Add config/database.yml to .gitignore
cp config/database.yml config/database.example.yml
git init
git add .
git commit -m "Inital Commit"
git remote add origin git@github.com:username/reponame
git push origin master

deployment

cap deploy:setup
# edit /home/username/apps/projectname/shared/config/database.yml on server
cap deploy:cold

after deploy:cold

sudo rm /etc/nginx/sites-enabled/default
sudo service nginx restart
sudo update-rc.d -f unicorn_projectname defaults

push changes to repo and deploy changes!

git push origin master
cap deploy

Resources from Railscasts/digital ocean documentation.
For use if puppet or chef is a little over your head.
I know you can bring up a droplet using rails, nginx, unicorn, and mysql, but you don't learn much that way!

Hopefully I didn't miss any steps, although I'm sure I did. Please leave comments if you run into troubles.

35 Responses
Add your response

7708
C32f157280a66059b1270f29fd5563da

thank you, very useful manual!

over 1 year ago ·
7716
Photo on 6 5 13 at 8.07 pm

You're more than welcome, hope it can be of some help to you!

over 1 year ago ·
7726
Kazt0eiopfm

Thanks :)

over 1 year ago ·
7930
F475074dea0fa3b7768751cd027d6540

Fantastic article... a great help. I did have a problem installing postgres until I added 91 to the name:
sudo apt-get install postgresql-9.1 postgresql-server-dev-9.1

The only other issue was there was no .ssh directory on the server. Creating keys on the server resolved this.

Excellent post!

over 1 year ago ·
8569
Ae2d8e04903dee021482e99cede6555e

Great article. And how about a hot deploy?

over 1 year ago ·
8571
Photo on 6 5 13 at 8.07 pm

This is a somewhat outdated article on zero downtime deployment I ran across. It could be of some help to you.

Zero downtime deployments with git capistrano nginx and unicorn

over 1 year ago ·
8991
202503571c050f8755e6a2ff551ca908

Hi, great article, but you have any idea how i can to do this without rvm?

Thanks!

over 1 year ago ·
8992
Photo on 6 5 13 at 8.07 pm

https://www.digitalocean.com/community/articles/how-to-install-ruby-on-rails-on-ubuntu-12-04-lts-with-rbenv--2

See the rbenv installation instructions section.

Or if you'd like to install from source

https://www.digitalocean.com/community/articles/how-to-install-ruby-on-rails-on-ubuntu-12-04-from-source

There's been quite a few write-ups on digital ocean since I wrote this, so try googling around. You can patch together resources.

You'll need to remove the rvm-capistrano gem, as well as the require line from your deploy.rb file. There are likely more settings you need to change, but you can follow the error messages. Shouldn't be too difficult.

over 1 year ago ·
9062
202503571c050f8755e6a2ff551ca908

Thanks!!

over 1 year ago ·
9265
94b4c5f9aeda227bf68245423e66f7ba

I used this to deploy my rails app to digital ocean's vps yesterday. I want to contribute to this for the deploy.rb file you need to add these three lines

set :rvmrubystring, :local

before 'deploy:setup', 'rvm:install_rvm' # install/update RVM

before 'deploy:setup', 'rvm:install_ruby' # install Ruby and create gemset, OR:

otherwise it will throw you a rvm can't be found error. Hope this helps someone.

over 1 year ago ·
10012
Profile pic

At the last step, when trying to restart nginx getting this error:
nginx: [emerg] unknown directive "upstream" in /etc/nginx/nginx.conf:1 ; nginx: configuration file /etc/nginx/nginx.conf test failed" when trying to restart nginx

Question asked here - http://stackoverflow.com/questions/19349057/nginx-unknown-directive-upstream

over 1 year ago ·
10014
Photo on 6 5 13 at 8.07 pm

http://stackoverflow.com/questions/7841612/nginx-unkown-directive-upstream

I am strapped for time, but I hope this helps. Be sure to post the solution if you find one! Thanks :)

over 1 year ago ·
10046
Profile pic

@james - my bad, it seems i got confused as to where i should put the conf file - http://stackoverflow.com/a/19384824/753705

over 1 year ago ·
10217

I think it might be worth mentioning that a 4G max on the client body size seems unnecessarily large to me for the average application. Maybe some apps could require such a sizable content transmission. I think being a little more conservative here could reduce exposure to easy DoS attacks. For example in the case I am currently building for even where PDF documents will regularly be transmitted in via API a 10M max seems totally viable.

I'm not knocking the author nor the configuration, it may be entirely valid and my concerns could be void as I'm far from masterfully knowledgable with Nginx.

over 1 year ago ·
10230
Photo on 6 5 13 at 8.07 pm

I appreciate you pointing that out. While I put this together to aid others in getting their applications deployed, I did not intend for it to be an end all guide. I would hope anyone who uses this guide, uses it as a boilerplate and customizes any settings to fit their needs. Anything that doesn't make sense, please review the nginx/unicorn/capistrano documentation.

Thanks for the comment sean!

over 1 year ago ·
10507
985d2865248f78c1b32ab6f26e38969b

It might be useful to mention this is Capistrano 2, and not version 3 you are using. They are quite different, and this can lead to confusion easily.

over 1 year ago ·
11046
Eeb90e7b92f78e01cac07087165e3640

Perhaps the content of JS environment is missed from this article??

over 1 year ago ·
11097
Eeb90e7b92f78e01cac07087165e3640

I refer the Nginx document and find lins below:

The default_server parameter has been available since version 0.8.21. In earlier versions the default parameter should be used instead.

I think the nginx.conf in this artical should change the default param to default_server param.

over 1 year ago ·
11904
0f39d1eb6c1dcb59aeabc8d5992c3023 normal

Anybody knows how to make it work with Capistrano 3.x?

over 1 year ago ·
12607
340cfcd8cad3ded0608a48f5999ef45e

stuck by Permission denied
when

* 2014-02-07 21:48:28 executing `deploy:start'
* executing "/etc/init.d/unicornexample start"
servers: ["example.com"]
[example.com] executing command
** [out :: example.com] bash: /etc/init.d/unicorn
example: Permission denied

it takes me several hours and nothing can work to solve it.

over 1 year ago ·
12812
0 htreaod8xqb9rfkachpcayd8ktlujuaag8fcay2tuq5w6h6lubikxpzyexakswrj3cjnor tc tm

im a newbie in this, so im not sure if is the best way or if its okay, but i fixed this adding this line:

run "chmod a+x /etc/init.d/unicorn_#{application}"

in the file deploy.rb before at line 27, i hope this help you.

over 1 year ago ·
12813
0 htreaod8xqb9rfkachpcayd8ktlujuaag8fcay2tuq5w6h6lubikxpzyexakswrj3cjnor tc tm

btw thanks for the manual :)

over 1 year ago ·
12848
11d948cbd083298dea9f4f32c71f6427

good stuff. I learned that the new capistrano (3.0) doesn't have deploy:cold anymore. You can just deploy. But it means you'll need a db:create to happen somewhere before your first deployment.

over 1 year ago ·
13817
10504ee74cc7e9984600710e51f08fc9

Does this work for Capistrano 3?

over 1 year ago ·
14598
D05d700ec84c34bb1be467fc2c906d31

i get error :
./config/deploy.rb:35:in read': No such file or directory @ rb_sysopen - config/database.example.yml (Errno::ENOENT) from ./config/deploy.rb:35:inblock (2 levels) in load'
from /Users/arifirmanto/.rvm/gems/ruby-2.1.1/gems/capistrano-2.15.5/lib/capistrano/configuration/execution.rb:138:in `instanceeval'
from /Users/arifirmanto/.rvm/gems/ruby-2.1.1/gems/capistrano-2.15.5/lib/capistrano/configuration/execution.rb:138:in `invoketaskdirectly'
from /Users/ari
firmanto/.rvm/gems/ruby-2.1.1/gems/capistrano-2.15.5/lib/capistrano/configuration/callbacks.rb:25:in invoke_task_directly_with_callbacks' from /Users/ari_firmanto/.rvm/gems/ruby-2.1.1/gems/capistrano-2.15.5/lib/capistrano/configuration/execution.rb:89:inexecutetask'
from /Users/ari
firmanto/.rvm/gems/ruby-2.1.1/gems/capistrano-2.15.5/lib/capistrano/configuration/execution.rb:101:in find_and_execute_task' from /Users/ari_firmanto/.rvm/gems/ruby-2.1.1/gems/capistrano-2.15.5/lib/capistrano/callback.rb:38:incall'
from /Users/arifirmanto/.rvm/gems/ruby-2.1.1/gems/capistrano-2.15.5/lib/capistrano/configuration/callbacks.rb:141:in `block in trigger'
from /Users/ari
firmanto/.rvm/gems/ruby-2.1.1/gems/capistrano-2.15.5/lib/capistrano/configuration/callbacks.rb:141:in each' from /Users/ari_firmanto/.rvm/gems/ruby-2.1.1/gems/capistrano-2.15.5/lib/capistrano/configuration/callbacks.rb:141:intrigger'
from /Users/arifirmanto/.rvm/gems/ruby-2.1.1/gems/capistrano-2.15.5/lib/capistrano/configuration/callbacks.rb:27:in `invoketaskdirectlywithcallbacks'
from /Users/ari
firmanto/.rvm/gems/ruby-2.1.1/gems/capistrano-2.15.5/lib/capistrano/configuration/execution.rb:89:in execute_task' from /Users/ari_firmanto/.rvm/gems/ruby-2.1.1/gems/capistrano-2.15.5/lib/capistrano/configuration/execution.rb:101:infindandexecutetask'
from /Users/ari
firmanto/.rvm/gems/ruby-2.1.1/gems/capistrano-2.15.5/lib/capistrano/cli/execute.rb:46:in block in execute_requested_actions' from /Users/ari_firmanto/.rvm/gems/ruby-2.1.1/gems/capistrano-2.15.5/lib/capistrano/cli/execute.rb:45:ineach'
from /Users/arifirmanto/.rvm/gems/ruby-2.1.1/gems/capistrano-2.15.5/lib/capistrano/cli/execute.rb:45:in `executerequestedactions'
from /Users/ari
firmanto/.rvm/gems/ruby-2.1.1/gems/capistrano-2.15.5/lib/capistrano/cli/help.rb:19:in execute_requested_actions_with_help' from /Users/ari_firmanto/.rvm/gems/ruby-2.1.1/gems/capistrano-2.15.5/lib/capistrano/cli/execute.rb:34:inexecute!'
from /Users/arifirmanto/.rvm/gems/ruby-2.1.1/gems/capistrano-2.15.5/lib/capistrano/cli/execute.rb:14:in `execute'
from /Users/ari
firmanto/.rvm/gems/ruby-2.1.1/gems/capistrano-2.15.5/bin/cap:4:in <top (required)>' from /Users/ari_firmanto/.rvm/gems/ruby-2.1.1/bin/cap:23:inload'
from /Users/arifirmanto/.rvm/gems/ruby-2.1.1/bin/cap:23:in `<main>'
from /Users/ari
firmanto/.rvm/gems/ruby-2.1.1/bin/rubyexecutablehooks:15:in eval' from /Users/ari_firmanto/.rvm/gems/ruby-2.1.1/bin/ruby_executable_hooks:15:in<main>'

when try to run cap deploy:setup

over 1 year ago ·
14599
Photo on 6 5 13 at 8.07 pm

From the stack trace, looks as though you don't have a database.example.yml file. Copy your database.yml file to a database.example.yml, removing any credentials.

over 1 year ago ·
14817

hi i am on local unicorn setup. when i try create unicorn.rb & unicorn_init.sh file
i get this error

[1] 15962
No command 'create' found, did you mean:
Command 'mcreate' from package 'lustre-utils' (universe)
create: command not found
unicorn_init.sh: command not found
[1]+ Exit 127 create unicorn.rb

over 1 year ago ·
14864
706e4a4e983d018be7ad544cc0e5e6e4

Awesome guide! Thanks!
I had to add Sidekiq for background processing. Is there a way to start it automatically on boot too?

over 1 year ago ·
15160
3e0552f8c75e2a0ac0dbf672aa7bd7e7

[ xx.xx.xxx.xx out] Error writing to authentication socket.
Stuck at this point while doing cap deploy:cold
Not able to figure out.
Could you please help? Where I might be wrong?

over 1 year ago ·
15165
Img 8235

Do you know if this will work for Linode as well?

over 1 year ago ·
16144
9b9bcf35274a8e24632c7c0c0887666b

I have done all the steps but while running the statement, I am getting the following error. I have created my Digital Ocean VPS host in port 3000.

$ sudo rm /etc/nginx/sites-enabled/default
$ sudo service nginx restart
* Restarting nginx nginx [fail]

$ sudo nginx -t

nginx: [emerg] "listen" directive is not allowed here in /etc/nginx/sites-enabled/hosting:1
nginx: configuration file /etc/nginx/nginx.conf test failed

over 1 year ago ·
16182
Explosionsinthesky websterhall   new york  2007

Here's a version of guide that uses puma instead of unicorn with Capistrano v3: Deploying Rails app using Nginx, Puma and Capistrano to Digital Ocean

over 1 year ago ·
18907
None

In your article the user should be typing "ssh git@github.com" and not "ssh github@github.com". Thanks for the article.

over 1 year ago ·
22838
None

While running the code, I am getting one error " [out :: xxx.xxx.xxx.xx] bash: bundle: command not found", I need help to resolve this. Its very urgent!!!

over 1 year ago ·
22839
Photo on 6 5 13 at 8.07 pm

AnujDubey,

This looks like an error with the bundler gem not being installed.

You can install bundler manually by ssh'ing into the server as the deploy user and running gem install bundler. Try the deployment after bundler has successfully installed.

BTW, this guide is out of date. Gorails.com has an excellent basic server setup guide which is much more up-to-date.

Hopefully this helps!

over 1 year ago ·