We all know how easy it is to create a Rails app, but what about when your app is ready for production?

The first thing you need to do is to set up your server and install the proper libraries, so fire up a terminal window and SSH into your server, after you have done this you will need to execute a few commands.

Configuring the server

Once you’re done configuring your server you need to install Ruby, for this you can either use RVM or rbenv, but I’m going to leave this choice to you.

Uploading to github

Next we’re going to upload our app to github. Head on over to the github and create a new repository.  Once you’ve done this, fire up a terminal on your local machine and “cd” into the root of your rails app.  Now, I’m going to assume you already have some experience with git commands.  If not, never fear, we’ve got this covered.  Once you’re in the root of your rails app run the following commands:

git init
git add .
git commit -m "<message>"
git remote add origin git@github.com:<username>/<git repo>.git
git push origin master

What we’ve done here is initialize an empty git repository inside the root of your rails app and add all your files to the git repo (If you need to ignore files you can edit the .gitignore file.) Next, we provided a message to the commit and added the remote origin, which will track our local development. Finally, we pushed the changes in our master branch to github.

Installing Capistrano

We’re almost there! It”s time to setup Capistrano for deployment, and the first step is too add it to the Gemfile:

gem 'capistrano'

Save the Gemfile and run the

bundle

command to install the gem. Next we’re going to capify our project, on a terminal window run:

capify .

This will create 2 files: one is the Cap file which will be placed in your root folder. By the way, if you’re using the rails asset pipeline, you will need to uncomment the line -load ‘deploy/assets’-. The second file is the deploy.rb file which will be placed inside the config folder of your rails app, In deploy.rb you will configure all the commands capistrano will be running on our remote server. You can copy and paste the example provided here:

require "bundler/capistrano"

# Define your server here
server "<server>", :web, :app, :db, primary: true

# Set application settings
set :application, "<app_name>"
set :user, "<deployment_user>" # As defined on your server
set :deploy_to, "/home/#{user}/apps/#{application}" # Directory in which the deployment will take place
set :deploy_via, :remote_cache
set :use_sudo, false

set :scm, "git"
set :repository, "git@github.com:<git_user>/#{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}" # Using unicorn as the app server
    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_ini.sh /etc/init.d/unicorn_#{application}"
    run "mkdir -p #{shared_path}/config"
    put File.read("config/database.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
view raw deploy.rb This Gist is brought to you using Simple Gist Embed.

As you can see, this deploy file uses unicorn as the server (which I recommend because of its stability, but you can use the server of your choice.) After you are finished setting up your deploy file with your custom configuration, create two files under your config folder: one named nginx.conf, and another one named unicorn.rb. Feel free to use the examples provided here:

Nginx.conf

upstream unicorn {
  server unix:/tmp/unicorn.<app_name>.sock fail_timeout=0;
}

server {
listen 80 default deferred;
server_name <your_servername>;
if ($host = '<your_servername>' ) {
rewrite ^/(.*)$ http://<your_servername>/$1 permanent;
}
  root /home/deployer/apps/<app_name>/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 4G;
  keepalive_timeout 10;
}

Unicorn.rb

# Define your root directory
root = "/home/deployer/apps/gifroll/current"

# Define worker directory for Unicorn
working_directory root

# Location of PID file
pid "#{root}/tmp/pids/unicorn.pid"

# Define Log paths
stderr_path "#{root}/log/unicorn.log"
stdout_path "#{root}/log/unicorn.log"

# Listen on a UNIX data socket
listen "/tmp/unicorn.gifroll.sock"

# 16 worker processes for production environment
worker_processes 16

# Load rails before forking workers for better worker spawn time
preload_app true

# Restart workes hangin' out for more than 240 secs
timeout 240

Final Steps

Create one final file under your config folder called unicorn_ini.sh edit the file and copy and paste the following code:

#!/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/deployer/apps/<app_name>/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=<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 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

We have all the files we need, so let’s setup the server to deploy our app.  Push the latest changes to github and after that run the following command:

cap deploy:setup

This will create 2 folders under the /<user>/apps/<app_name> on your server. as well as some links for the unicorn and nginx configration files to place them in the proper location. After the setup is complete “cd” into the /<user>/apps/<app_name>/shared/config and edit the database.yml file to work with your database.

Once you’ve done this, give the server access to the git repository by running the following command on our development machine:

ssh-add

Now we’re all setup and ready to deploy. Let’s try this out by running:

cap deploy:cold

And watch it go.  A “cold” deploy will run the migrations and do a server start and restart.  In my experience, it will rarely work the first time you will usually get some errors here and there.  The errors will usually point you in the right direction.

If everything went OK, congratulations!  You just deployed your app using unicorn, nginx and capistrano. Hope you liked it!