I. Server Setup

  1. Create deployer account on server
adduser deployer
  1. Add user to sudo group
gpasswd -a deployer sudo
  1. Add ability to execute sudo without password
visudo

Add and save

deployer ALL=(ALL) NOPASSWD: ALL
  1. Add Public Key Authentication (On local machine)
ssh-copy-id -i PATH_TO_KEY deployer@SERVER_IP
  1. Disable password based ssh login
nano /etc/ssh/sshd_config

Update following lines

ChallengeResponseAuthentication no
PasswordAuthentication no
UsePAM no

Restart sshd daemon

service sshd restart
  1. Create dirs
mkdir /www
chown deployer:deployer /www

II. Deploy config

  1. Install gems
group :development do
  gem 'capistrano', '~> 3.4.0'
  gem 'capistrano-rails'
  gem 'capistrano-bundler'
  gem 'capistrano-deploytags'
  gem 'rvm1-capistrano3', require: false
  gem 'capistrano-nginx-unicorn', github: 'timoshenkoav/capistrano-nginx-unicorn'
end
bundle install
bundle exec cap install
  1. Configure deploy.rb
lock '3.4.0'

set :application, 'APPLICATION_NAME'
set :repo_url, 'APPLICATION_GIT_URL'

set :branch, 'master'

set :deploy_to, "/var/www/#{fetch(:application)}"

set :scm, :git

set :linked_files, fetch(:linked_files, []).push('config/database.yml', 'config/application.yml')

set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system', 'public/uploads', 'stats')

set :rvm1_type, :user
set :rvm1_alias_name, "#{fetch(:application)}"
set :rvm1_ruby, 'RUBY_VERSION'
set :rvm1_ruby_version, "#{fetch(:rvm1_ruby)}@#{fetch(:rvm1_alias_name)}"

set :keep_releases, 2
namespace :setup do
  task :create_env do
    on roles(:all) do |host|
      execute :mkdir, '-p', "#{shared_path}/config"
      ask(:env_file, 'File to get env')
      upload! fetch(:env_file), "#{shared_path}/config/application.yml"
    end

  end
  task :create_db do
    on roles(:all) do |host|
      execute :mkdir, '-p', "#{shared_path}/config"
      ask(:env_file, 'File to get database.yml')
      upload! fetch(:env_file), "#{shared_path}/config/database.yml"
    end
  end
  task :create_secrets do
    on roles(:all) do |host|
      execute :mkdir, '-p', "#{shared_path}/config"
      ask(:env_file, 'File to get secrets.yml')
      upload! fetch(:env_file), "#{shared_path}/config/secrets.yml"
    end
  end
end

namespace :app do
  task :update_rvm_key do
    on roles(:all) do
      execute :gpg, '--keyserver hkp://keys.gnupg.net --recv-keys D39DC0E3'
    end

  end
end
before 'rvm1:install:rvm', 'app:update_rvm_key'

2.1 Configure Capfile

require 'capistrano/setup'

require 'capistrano/deploy'
require 'rvm1/capistrano3'

require 'capistrano/nginx_unicorn'
require 'capistrano/bundler'
require 'capistrano/rails/assets'
require 'capistrano/rails/migrations'
require 'capistrano/monit'
Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }
  1. Configure config/deploy/production.rb
role :app, %w(SERVER_IP)
role :web, %w(SERVER_IP)
role :db, %w(SERVER_IP)
role :worker, %w(SERVER_IP)

set :rails_env, 'production'

set :ssh_options, {
    user: 'deployer',
    keys: %w(PATH_TO_USER_KEY),
    forward_agent: true,
    auth_methods: %w(publickey)
}

#NGINX
set :templates_path, 'config/deploy/templates'
set :nginx_config_name, 'NGINX_CONFIG_FILE_NAME'
set :nginx_server_name, 'NGINX_SERVER_NAME'
set :nginx_pid, '/run/nginx.pid'

set :nginx_config_path, '/etc/nginx/sites-available'

set :unicorn_workers, 2
set :unicorn_user, 'deployer'
set :unicorn_log, "#{shared_path}/log/unicorn.log"
set :unicorn_config, "#{shared_path}/config/unicorn.rb"
set :unicorn_pid, "#{current_path}/tmp/pids/unicorn.pid"
set :unicorn_sock, "#{shared_path}/tmp/sockets/unicorn.sock"
  1. Configure server templates

4.1 deploy/templates/unicorn.rb.erb

working_directory "<%= current_path %>"
pid "<%= fetch(:unicorn_pid) %>"
stderr_path "<%= fetch(:unicorn_log) %>"
stdout_path "<%= fetch(:unicorn_log) %>"

listen "<%= fetch(:unicorn_sock) %>"
worker_processes <%= fetch(:unicorn_workers) %>
timeout 600

preload_app true

before_exec do |server|
  ENV["BUNDLE_GEMFILE"] = "<%= current_path %>/Gemfile"
end

before_fork do |server, worker|
  # Disconnect since the database connection will not carry over
  if defined? ActiveRecord::Base
    ActiveRecord::Base.connection.disconnect!
  end

  # Quit the old unicorn process
  old_pid = "#{server.config[:pid]}.oldbin"
  if File.exists?(old_pid) && server.pid != old_pid
    begin
      Process.kill("QUIT", File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH
      # someone else did our job for us
    end
  end

  if defined?(Resque)
    Resque.redis.quit
  end

  sleep 1
end

after_fork do |server, worker|
  # Start up the database connection again in the worker
  if defined?(ActiveRecord::Base)
    ActiveRecord::Base.establish_connection
  end

  if defined?(Resque)
    Resque.redis = 'localhost:6379'
  end
end

4.2 deploy/templates/unicorn_init.rb

#!/bin/bash
### 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=<%= current_path %>
PID=<%= fetch(:unicorn_pid) %>

# rvm
CMD="cd <%= current_path %>; rvm <%= fetch(:rvm1_ruby_version) %> do bundle exec unicorn -D -c <%= fetch(:unicorn_config) %> -E <%= fetch(:rails_env) %>"

AS_USER=<%= fetch(:unicorn_user) %>
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 USR2 && 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

4.3 deploy/templates/nginx_conf.erb

upstream unicorn_<%= fetch(:nginx_config_name) %> {
server unix:<%= fetch(:unicorn_sock) %> fail_timeout=0;
}


server {

listen 80;

client_max_body_size 4G;
keepalive_timeout 10;

error_page 500 502 504 /500.html;
error_page 503 @503;

server_name <%= fetch(:nginx_server_name) %>;
root <%= current_path %>/public;
try_files $uri/index.html $uri @unicorn_<%= fetch(:nginx_config_name) %>;

gzip              on;
gzip_http_version 1.0;
gzip_disable      "MSIE [1-6]\.(?!.*SV1)";
gzip_buffers 4 16k;
gzip_comp_level 2;
gzip_min_length 0;
gzip_types text/plain text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript application/json;
gzip_proxied any;

location @unicorn_<%= fetch(:nginx_config_name) %> {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
add_header Access-Control-Allow-Origin *;
proxy_pass http://unicorn_<%= fetch(:nginx_config_name) %>;
# limit_req zone=one;
access_log <%= shared_path %>/log/nginx.access.log;
error_log <%= shared_path %>/log/nginx.error.log;
}

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

location = /50x.html {
root html;
}

location = /404.html {
root html;
}

location @503 {
error_page 405 = /system/maintenance.html;
if (-f $document_root/system/maintenance.html) {
rewrite ^(.*)$ /system/maintenance.html break;
}
rewrite ^(.*)$ /503.html break;
}

if ($request_method !~ ^(GET|HEAD|PUT|POST|DELETE|OPTIONS|PATCH)$ ){
return 405;
}

if (-f $document_root/system/maintenance.html) {
return 503;
}

}
  1. Deploy setup

5.1 Install RVM and ruby

 bundle exec cap production rvm1:install:rvm
 bundle exec cap production rvm1:install:ruby

5.2 Create gemset

Edit file lib/capistrano/tasks/create_gemset.rake

namespace :gemset do
  desc "Create an gemset for the given"
  task :create do
    on roles(fetch(:rvm1_roles, :all)) do
      within fetch(:release_path) do
        execute "#{fetch(:rvm1_auto_script_path)}/rvm-auto.sh",
                "rvm", "ruby-#{fetch(:rvm1_ruby)}",'do','rvm','gemset','create',
          fetch(:rvm1_alias_name)
      end
    end
  end
  before :create, 'deploy:updating'
  before :create, 'rvm1:hook'
end
 bundle exec cap production gemset:create

5.3 Create alias

 bundle exec cap production rvm1:alias:create

5.4 Install Bundler

Edit file lib/capistrano/tasks/install_bundle.rake

namespace :bundle do
  desc "Create an gemset for the given"
  task :install do
    on roles(fetch(:rvm1_roles, :all)) do
      within release_path do
        execute "#{fetch(:rvm1_auto_script_path)}/rvm-auto.sh",fetch(:rvm1_ruby_version), "gem", "install", "bundler"
      end
    end
  end
  before :install, 'deploy:updating'
  before :install, 'rvm1:hook'
end
 bundle exec cap production bundle:install

5.5 Push config and env

 bundle exec cap production setup:create_env
 bundle exec cap production setup:create_db
 bundle exec cap production setup:create_secrets

5.6 Edit copied application.yml, database.yml and secrets.yml for on server

  1. Install nginx and postgres
apt-get install nginx postgresql postgresql-contrib libpq-dev
  1. Create postgres user
sudo -u postgres createuser -s appname
sudo -u postgres psql
\password appname
CREATE DATABASE DB_NAME owner appname;
\q
  1. Deploy nginx and capistrano configuration
bundle exec cap production nginx:setup
bundle exec cap production unicorn:setup_initializer
bundle exec cap production unicorn:setup_app_config
  1. Install requirements for ember
apt-get install nodejs nodejs-legacy npm
npm install -g bower
  1. Deploy
bundle exec cap production deploy
  1. Start
bundle exec cap production unicorn:start