Deploying Rails Apps using Capistrano (Part 2)

Prerequisite: Deploying Rails Apps using Capistrano (Part 1)

Table of Contents

 

3 Installing Software

Now let’s make this VPS useful!

 

3.1 Installing Ruby using RVM

To install Ruby, I prefer to perform a custom Ruby installation rather than installing the packaged Ruby that is available via the Ubuntu sources. This gives me the freedom to pick the exact version/patch that I want to install, depending on the requirements of the application I’m planning to install on the server. To do so, I use rvm, or Ruby Version Manager. An alternative to rvm is rbenv, but I kind of got used to using rvm, as I knew about it before rbenv.

To install rvm, run the following command.

curl -sSL https://get.rvm.io | bash -s stable

To load rvm for the current session, run the following command.

source /home/deployer/.rvm/scripts/rvm

After installing rvm, let’s use it to install the latest version of ruby. As of the time of writing this post, the latest stable version of ruby was 2.1.0, so let’s install it by running the following command.

rvm install 2.1.0

After the installation is complete, run the following command to make sure everything went as expected. This should print out the current version of ruby installed, and it should be ….. yup, that’s right! 2.1.0!

ruby -v

 

3.2 Installing Phusion Passenger

After installing our main software, Ruby, let’s proceed with the next most important piece of software, Phusion Passenger, the most well-known application server for Ruby, IMHO. Luckily, it’s package as a Ruby gem, which makes it very easy to install. But first, let’s make sure our gem management software is up to date, by running the following command.

gem update --system

Usually, whenever a gem is installed, it’s documentation is also downloaded and installed alongside, which is helpful, of course, but consumes both time and space when all what is needed is to install the gem itself. Documentations are available online, and I don’t find a need to have them locally. To prevent this, run the following command.

echo gem: --no-rdoc --no-ri > ~/.gemrc

To install Passenger, run the following command. Let’s also install/update bundler while we’re at it.

gem install bundler passenger

After the installation is complete, two new executables become available, passenger-install-nginx-module and passenger-install-apache2-module, which provide an automated way to integrate Passenger with Nginx and Apache 2 respectively. Both of these are web servers that can work hand in hand with Passenger to put Ruby on Rails applications online.

For the sake of the tutorial, I’ll be installing Nginx, in favor of Apache 2, because of its consistent low memory footprint.

 

3.3 Installing Nginx

To install Nginx, simple use the utility provided by the passenger gem. Run the following command.

rvmsudo passenger-install-nginx-module

The installer will guide you through the steps to install and configure both Nginx and Passenger. First off, it’ll ask you about the languages you’re interested in. If you’re building a development server, I see no harm in choosing all of them, but for the sake of this tutorial, I’ll just choose Ruby.

The utility will do some dependency checks and inform you if anything is needed to be done before continuing. Just follow the steps provided by the utility and wait till it finishes. In my case, curl development headers were missing, so I had to run the following command to install them before I could continue.

sudo aptitude install libcurl4-openssl-dev

Rerun passenger-install-nginx-module and follow the steps shown. When you reach the step where it asks you about Nginx’ installation, pick the recommended method, ‘Yes: download, compile and install Nginx for me.’, and agree to all the questions that it asks you, unless you know what you’re doing.

 

3.3.1 Configuring Nginx

By default, Nginx get installed in /opt/nginx, so let’s cd into the directory and make some changes. I’ll assume that you used the default installation directory in the following snippets of code, so make sure you amend them accordingly if you used a different path.

cd /opt/nginx

Open up conf/nginx.conf and replace its contents with the following. Make sure to note the version numbers in the commented lines in your copy of conf/nginx.conf before replacing them.

user             www-data;
worker_processes 4;

events {
    worker_connections  1024;
}

http {
    # Make sure to use your versions of ruby and passenger.
    passenger_root /home/deployer/.rvm/gems/ruby-2.1.0@global/gems/passenger-4.0.41;
    passenger_ruby /home/deployer/.rvm/gems/ruby-2.1.0/wrappers/ruby;

    include            mime.types;
    default_type       application/octet-stream;

    sendfile           on;
    tcp_nopush         on;
    tcp_nodelay        off;

    keepalive_timeout  65;

    gzip               on;
    gzip_comp_level    2;
    gzip_proxied       any;
    gzip_types         text/plain text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript;

    include /opt/nginx/sites/enabled/*;
}

Following the convention Apache uses in organizing its website definitions, let’s create the following directories.

sudo mkdir -p /opt/nginx/sites/available /opt/nginx/sites/enabled

The sites/available contains all available websites and the sites/enabled contains only enabled websites, which get included at the end of conf/nginx.conf automatically.

Let’s create our first website! Go to sites/available and create a file called v.ps, or whatever your website domain name looks like, then open it up in your favorite editor. Paste the following in it.

server {
    listen 80;

    server_name v.ps www.v.ps;
    root        /home/deployer/public_html/v.ps/current/public;

    access_log /home/deployer/public_html/v.ps/current/log/access.log;
    error_log  /home/deployer/public_html/v.ps/current/log/error.log;

    passenger_enabled on;
}

Save the file and exit, then go to sites/enabled and run the following command. This creates a symlink of the website definition we just created in the sites/enabled directory, which makes it visible to nginx’ configuration. This eliminates the need to duplicate the file.

sudo ln -s /opt/nginx/sites/available/v.ps /opt/nginx/sites/enabled/v.ps

In the website definition file we just created, I assumed that a directory called exists, which holds the content of our website. For the time being, let’s create this directory manually and put in a dummy index.html page. This step will be automated when deploying using capistrano. Also, let’s create some other directories that are mentioned in the file.

mkdir -p /home/deployer/public_html/v.ps/current
cd /home/deployer/public_html/v.ps/current/
mkdir public log
echo '<!DOCTYPE html><html><head><title>V.PS</title></head><body><h1>Welcome to V.PS</h1></body></html>' > public/index.html

Since we didn’t use packaged version of Nginx, we need to manually configure it to start automatically whenever the server is booted. This requires us to create an init.d script and make it executable.

sudo touch /etc/init.d/nginx
sudo chmod a+x /etc/init.d/nginx
sudo update-rc.d -f nginx defaults

Paste the following into /etc/init.d/nginx.

#! /bin/sh
### BEGIN INIT INFO
# Provides:          nginx
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: nginx init.d dash script for Ubuntu or other *nix.
# Description:       nginx init.d dash script for Ubuntu or other *nix.
### END INIT INFO
#------------------------------------------------------------------------------
# nginx - this Debian Almquist shell (dash) script, starts and stops the nginx
#         daemon for Ubuntu and other *nix releases.
#
# description:  Nginx is an HTTP(S) server, HTTP(S) reverse 
#               proxy and IMAP/POP3 proxy server.  This 
#       script will manage the initiation of the 
#       server and it's process state.
#
# processname: nginx
# config:      /opt/nginx/conf/nginx.conf
# pidfile:     /opt/nginx/logs/nginx.pid
# Provides:    nginx
#
# Author:  Jason Giedymin
#          .
#
# Version: 2.0 02-NOV-2009 jason.giedymin AT gmail.com
# Notes: nginx init.d dash script for Ubuntu.
#
# This script's project home is:
#   http://github.com/JasonGiedymin/nginx-init-ubuntu
#
#------------------------------------------------------------------------------
#                               MIT X11 License
#------------------------------------------------------------------------------
#
# Copyright (c) 2009 Jason Giedymin, http://jasongiedymin.com
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
#                               Functions
#------------------------------------------------------------------------------
. /lib/lsb/init-functions

#------------------------------------------------------------------------------
#                               Consts
#------------------------------------------------------------------------------
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/opt/nginx/sbin/nginx

PS="nginx"
PIDNAME="nginx"                     #lets you do $PS-slave
PIDFILE=$PIDNAME.pid                #pid file
PIDSPATH=/opt/nginx/logs

DESCRIPTION="Nginx Server..."

RUNAS=root                          #user to run as

SCRIPT_OK=0                         #ala error codes
SCRIPT_ERROR=1                      #ala error codes
TRUE=1                              #boolean
FALSE=0                             #boolean

lockfile=/var/lock/subsys/nginx
NGINX_CONF_FILE="/opt/nginx/conf/nginx.conf"

#------------------------------------------------------------------------------
#                               Simple Tests
#------------------------------------------------------------------------------

#test if nginx is a file and executable
test -x $DAEMON || exit 0

# Include nginx defaults if available
if [ -f /etc/default/nginx ] ; then
    . /etc/default/nginx
fi

#set exit condition
#set -e

#------------------------------------------------------------------------------
#                               Functions
#------------------------------------------------------------------------------

setFilePerms(){
    if [ -f $PIDSPATH/$PIDFILE ]; then
        chmod 400 $PIDSPATH/$PIDFILE
    fi
}

configtest() {
    $DAEMON -t -c $NGINX_CONF_FILE
}

getPSCount() {
    return `pgrep -f $PS | wc -l`
}

isRunning() {
    if [ $1 ]; then
        pidof_daemon $1
        PID=$?

        if [ $PID -gt 0 ]; then
            return 1
        else
            return 0
        fi
    else
        pidof_daemon
        PID=$?

        if [ $PID -gt 0 ]; then
            return 1
        else
            return 0
        fi
    fi
}

#courtesy of php-fpm
wait_for_pid () {
    try=0

    while test $try -lt 35 ; do
        case "$1" in
            'created')
            if [ -f "$2" ] ; then
                try=''
                break
            fi
            ;;

            'removed')
            if [ ! -f "$2" ] ; then
                try=''
                break
            fi
            ;;
        esac

        try=`expr $try + 1`
        sleep 1
    done
}

status(){
    isRunning
    isAlive=$?

    if [ "${isAlive}" -eq $TRUE ]; then
        echo "$PIDNAME found running with processes:  `pidof $PS`"
    else
        echo "$PIDNAME is NOT running."
    fi
}

removePIDFile(){
    if [ $1 ]; then
        if [ -f $1 ]; then
            rm -f $1
        fi
    else
        #Do default removal
        if [ -f $PIDSPATH/$PIDFILE ]; then
            rm -f $PIDSPATH/$PIDFILE
        fi
    fi
}

start() {
    log_daemon_msg "Starting $DESCRIPTION"

    isRunning
    isAlive=$?

    if [ "${isAlive}" -eq $TRUE ]; then
        log_end_msg $SCRIPT_ERROR
    else
        start-stop-daemon --start --quiet --chuid 
        $RUNAS --pidfile $PIDSPATH/$PIDFILE --exec $DAEMON 
        -- -c $NGINX_CONF_FILE
        setFilePerms
        log_end_msg $SCRIPT_OK
    fi
}

stop() {
    log_daemon_msg "Stopping $DESCRIPTION"

    isRunning
    isAlive=$?

    if [ "${isAlive}" -eq $TRUE ]; then
        start-stop-daemon --stop --quiet --pidfile $PIDSPATH/$PIDFILE

        wait_for_pid 'removed' $PIDSPATH/$PIDFILE

        if [ -n "$try" ] ; then
            log_end_msg $SCRIPT_ERROR
        else
            removePIDFile
            log_end_msg $SCRIPT_OK
        fi
    else
        log_end_msg $SCRIPT_ERROR
    fi
}

reload() {
    configtest || return $?

    log_daemon_msg "Reloading (via HUP) $DESCRIPTION"

    isRunning

    if [ $? -eq $TRUE ]; then
        `killall -HUP $PS` #to be safe
        log_end_msg $SCRIPT_OK
    else
        log_end_msg $SCRIPT_ERROR
    fi
}

quietupgrade() {
    log_daemon_msg "Peforming Quiet Upgrade $DESCRIPTION"

    isRunning
    isAlive=$?

    if [ "${isAlive}" -eq $TRUE ]; then
        kill -USR2 `cat $PIDSPATH/$PIDFILE`
        kill -WINCH `cat $PIDSPATH/$PIDFILE.oldbin`

        isRunning
        isAlive=$?

        if [ "${isAlive}" -eq $TRUE ]; then
            kill -QUIT `cat $PIDSPATH/$PIDFILE.oldbin`
            wait_for_pid 'removed' $PIDSPATH/$PIDFILE.oldbin
                        removePIDFile $PIDSPATH/$PIDFILE.oldbin

            log_end_msg $SCRIPT_OK
        else
            log_end_msg $SCRIPT_ERROR

            log_daemon_msg "ERROR! Reverting back to original $DESCRIPTION"

            kill -HUP `cat $PIDSPATH/$PIDFILE`
            kill -TERM `cat $PIDSPATH/$PIDFILE.oldbin`
            kill -QUIT `cat $PIDSPATH/$PIDFILE.oldbin`

            wait_for_pid 'removed' $PIDSPATH/$PIDFILE.oldbin
            removePIDFile $PIDSPATH/$PIDFILE.oldbin

            log_end_msg $SCRIPT_ok
        fi
    else
            log_end_msg $SCRIPT_ERROR
    fi
}

terminate() {
    log_daemon_msg "Force terminating (via KILL) $DESCRIPTION"

    PIDS=`pidof $PS` || true

    [ -e $PIDSPATH/$PIDFILE ] && PIDS2=`cat $PIDSPATH/$PIDFILE`

    for i in $PIDS; do
        if [ "$i" = "$PIDS2" ]; then
            kill $i
            wait_for_pid 'removed' $PIDSPATH/$PIDFILE
            removePIDFile
        fi
    done

    log_end_msg $SCRIPT_OK
}

destroy() {
    log_daemon_msg "Force terminating and may include self (via KILLALL) $DESCRIPTION"
    killall $PS -q >> /dev/null 2>&1
    log_end_msg $SCRIPT_OK
}

pidof_daemon() {
    PIDS=`pidof $PS` || true

    [ -e $PIDSPATH/$PIDFILE ] && PIDS2=`cat $PIDSPATH/$PIDFILE`

    for i in $PIDS; do
        if [ "$i" = "$PIDS2" ]; then
            return 1
        fi
    done

    return 0
}

case "$1" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    restart|force-reload)
        stop
        sleep 1
        start
        ;;
    reload)
        $1
        ;;
    status)
        status
        ;;
    configtest)
        $1
        ;;
    quietupgrade)
        $1
        ;;
    terminate)
        $1
        ;;
    destroy)
        $1
        ;;
    *)
        FULLPATH=/etc/init.d/$PS
        echo "Usage: $FULLPATH {start|stop|restart|force-reload|status|configtest|quietupgrade|terminate|destroy}"
        echo "       The 'destroy' command should only be used as a last resort."
        exit 1
        ;;
esac

exit 0

Now run the following command to start Nginx, and head over to http://v.ps/ to see the results of your creations so far.

sudo service nginx start

 

References

Ramy Aboul Naga (@RaMin0)

A senior web apps developer with 6+ years of experience, who seeks to enrich the aspects of the web, to make it a more friendly environment for those who experience its advantages and nourishing knowledgebase.

9 thoughts to “Deploying Rails Apps using Capistrano (Part 2)”

  1. І’ll right away clutch your rss feed as I can not find your
    e-mail ѕuЬscription link or e-newsletter serѵiсe. Do уou’ve any?
    Pleaѕe allow me recogniѕe so that I may ѕubsсriƅe.

    Thankѕ.

  2. Hellо would you miոd letting mе know which hosting company you’гe սtilizing?

    I’ve loaded your blog in 3 different browsers and I must say tɦis blog loads a lot quicker then most.
    Can yoս suggest a goߋd iոternet hosting pгovider at a
    fair prіϲe? Tɦanks, I appreciate it!

  3. I am not sure where you are getting your info, but great topic. I needs to spend some time learning more or understanding more. Thanks for wonderful info I was looking for this information for my mission.|

  4. Thanks on your marvelous posting! I definitely enjoyed reading it,
    you’re a great author. I will make certain to bookmark
    your blog and definitely will come back down the road.
    I want to enciurage that yoou continue your great work,
    haqve a nice evening!

    Look ito my homepage … levante cream (Dan)

Leave a Reply

Your email address will not be published. Required fields are marked *