Skip to main content

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

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


Create droplet of your liking (ubuntu 12.10 x32)

ssh to root in terminal with your server ip

ssh root<a href="/123">@123</a>.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<a href="/123">@123</a>.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<a href="/123">@123</a>.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.

Comments

Popular posts from this blog

Error malloc(): memory corruption nginx with passenger?

Error malloc(): memory corruption nginx with passenger Passenger issue resolving steps :  sudo gem uninstall passenger(uninstall all passenger) sudo gem install passenger sudo passenger-install-nginx-module --auto --auto-download --prefix=/opt/nginx --extra-configure-flags=none Update nginx config file with new passenger version and restart the nginx

Lazy loading in rails – Rails Feature

 Lazy loading in rails – Rails Feature ? Lazy loading in rails is the amazing feature provided with rails. In console you might have tried to examine how lazy loading in rails actually works. In this tutorial, we will learn about this Rails - Lazy loading feature with examples. What exactly is Lazy Loading? As the name suggests the data is loaded in lazy manner (Really!) i.e. Your database is queried only when data from the database is required for some kind of manipulation in code. You will get more of this after you read how-to of lazy loading below. How lazy loading works: Whenever you try to get some data from database, For example, users is the database table that you have. And you are querying database to get users having age less than 20. Then, you will write code like, result = User.where("age < 20") when above statement is executed, your database is not queries yet(because the resultant data is not required yet). When you execute following code, records = resu...

Rails Migration Difference between Text and String

Rails Migration Difference between Text and String ? While working with Rails Migration Difference between Text and String is important to be known to every developer. Columns and their data types are finalized while deciding Table structure. This tutorial will help understand difference between String and Text column type and illustrate how to write Rails Migration implementing the same. You might want to read about database.yml files for specifying database configuration for Rails Application. 1. Concepts When String or Text data type is required?     Whenever you require your column to store information which is lengthy in size (Many characters), you need to consider String or Text data type for the column.     Both of them let you store Many(How Many - will see later) characters Difference between String and Text Considering MySQL database Feature     String     Text Length     1 to 255     ...