Useful Links
Show All Blog Articles

Ubuntu, Rails ready, with nginx & unicorn

Fri, 11 Jul 2014

How to set up an Ubuntu Linux box to be ready for hosting Ruby on Rails, with nginx & unicorn.

Here we go...

We'll be using Digital Ocean to set up the Ubuntu virtural box, a droplet in their terms. Give the droplet a hostname, select a size, a region, Ubuntu 14.04 x64 for the image, than click Create Droplet. In a few minutes, you'll get an email with the IP address and root password.

Add a sudo user

With great power comes great responsibility. I prefer to authenticate with a sudo user (an user with administrator rights), instead of the root user and prefix commands with sudo and get prompted to enter the password, when required.

ssh <IP address provided>
adduser <username>
adduser <username> sudo
exit

At this point, we should be able to log in back with newly created sudo user.

Update Ubuntu with latest packages

Let's make sure we have latest packages installed:

sudo apt-get update
sudo apt-get upgrade

Install PostgreSQL

This step is optional, as we prefer PostreSQL, especially in production.

sudo apt-get install postgresql postgresql-contrib
sudo -u postgres psql postgres

Within, postgres console, enter \password postgres to setup a password, and \q to get back to the Ubuntu command line.

Create a database:

sudo -u postgres createdb <database-name>

Alternatively, if you prefer to stay with sqlite3, it will come along with Ruby on Rails in the next step.

Install Ruby & Ruby on Rails

First of all, let's see, do we have a Ruby pre-installed?

ruby -v

Nope, and here it comes the suggestion, which packages we may use to bring ruby on board.

The program 'ruby' can be found in the following packages:
 * ruby
 * ruby1.8
Try: sudo apt-get install <selected package>

But, no, we will use rvm to manage our rubies, and below is the command to bring latest stable ruby, alog with latest stable Ruby on Rails.

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

The leading backslash is required to ensure that we are using the regular curl command and not any altered, aliased version.

Run the source command suggested at the end of the installation process, or logout/exit and log in again.

Now, both Ruby and Ruby on Rails are installed, go and check:

ruby -v
rails -v

Rails will also need a JavaScript runtime. For this, we're going to install... node.js, yep.

sudo apt-get install nodejs

At this point you may quickly create a Rails app and check how it runs on port 3000. Next on our list: nginx.

Install a source code versioning manager

Nowdays, almost everyone goes with git. It will help manage your source code whilst developing, and may come useful when you deploy, too.

sudo apt-get install git

Install nginx

sudo apt-get install nginx

Start your browser, and head to http://<IP address>. Default nginx page should prompt you.

It is managed by sites-available / sites-enabled conf files. Let's take a look:

/etc/nginx/sites-available
ls
cat default

We'll come back later to change this file, or if you prefer, create a secondary sites-available conf file and link it to sites-enables. But now, it is the time to give some attention to our unicorns.

Install Unicorn

First of all, what are ruby gems?

RubyGems is a package manager for the Ruby programming language that provides a standard format for distributing Ruby programs and libraries (in a self-contained format called a "gem"), a tool designed to easily manage the installation of gems, and a server for distributing them. -- Wikipedia

Yes, you guess it right, we'll be using ruby's package manager to install unicorn:

gem install unicorn

Configurations

/var/www/ is one of the prefered location to install/deploy our apps and I would like to have access to it without having to sudo each time. For this, let's change the owner of www folder.

sudo chown <username> /var/www/

Let's try an application:

cd /var/www
rails new demo-app
cd demo-app
bundle install
rake db:migrate
rails server

It should work and have access to it, however, let's start our unicorns... As a first step, we'll need to add gem 'unicorn' to our Gemfile and re-run bundle install command. Let's start the server again, using unicorn_rails. And, it works, or at least it should be!

unicorn_rails

Now, let's create a configuration file for unicorn, in conf/unicorn.rb.

# config/unicorn.rb

# Set the working application directory
# working_directory '/path/to/your/app'
working_directory '/var/www/demo-app'

# Unicorn PID file location
# pid '/path/to/pids/unicorn.pid'
pid '/var/www/demo-app/pids/unicorn.pid'

# Path to logs
# stderr_path '/path/to/log/unicorn.log'
# stdout_path '/path/to/log/unicorn.log'
stderr_path '/var/www/demo-app/log/unicorn.log'
stdout_path '/var/www/demo-app/log/unicorn.log'

# Unicorn socket
# listen '/tmp/unicorn.[application name].sock'
listen '/tmp/unicorn.demo-app.sock'

# Number of processes
# worker_processes 4
worker_processes 2

# Time-out
timeout 30

... and the pids folder (full path in our case /var/www/demo-app/pids) ...

mkdir pids

Remember the default file we talked about earlier? Backup the original, and let's edit it:

sudo vim /etc/nginx/sites-available/default

Its content should read now:

upstream app {
    # Path to Unicorn SOCK file, as defined previously
    server unix:/tmp/unicorn.demo-app.sock fail_timeout=0;
}

server {
    listen 80;
    server_name localhost;

    # Application root, as defined previously
    root /var/www/demo-app/public;

    try_files $uri/index.html $uri @app;

    location @app {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_pass http://app;

    }

    error_page 500 502 503 504 /500.html;
    client_max_body_size 4G;
    keepalive_timeout 10;
}

Let's try it out:

cd /var/www/demo-app
unicorn_rails -c config/unicorn.rb -D
sudo service nginx restart

And it should work. There is one last step, and this is to create init.d script for unicorn, to start up automatically on system reboot. Let's create a file config/unicorn_init.sh.

#!/bin/sh
set -e
# Example init script, this can be used with nginx, too,
# since nginx and unicorn accept the same signals

# Feel free to change any of the following variables for your app:
TIMEOUT=${TIMEOUT-60}
APP_ROOT=/var/www/demo-app
PID=$APP_ROOT/var/www/demo-app/pids/unicorn.pid
CMD="$APP_ROOT/bin/unicorn -D -c $APP_ROOT/config/unicorn.rb -E production"
action="$1"
set -u

old_pid="$PID.oldbin"

cd $APP_ROOT || exit 1

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

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

case $action in
start)
        sig 0 && echo >&2 "Already running" && exit 0
        su -c "$CMD" - <username>
        ;;
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"
        su -c "$CMD" - <username>
        ;;
upgrade)
        if sig USR2 && sleep 2 && sig 0 && oldsig QUIT
        then
                n=$TIMEOUT
                while test -s $old_pid && test $n -ge 0
                do
                        printf '.' && sleep 1 && n=$(( $n - 1 ))
                done
                echo

                if test $n -lt 0 && test -s $old_pid
                then
                        echo >&2 "$old_pid still exists after $TIMEOUT seconds"
                        exit 1
                fi
                exit 0
        fi
        echo >&2 "Couldn't upgrade, starting '$CMD' instead"
        su -c "$CMD" - <username>
        ;;
reopen-logs)
        sig USR1
        ;;
*)
        echo >&2 "Usage: $0 "
        exit 1
        ;;
esac

We need to make this file executable:

sudo chmod +x config/unicorn_init.sh

Copy this file as /etc/init.d/unicorn or symlink it.

sudo update-rc.d unicorn defautls

to start automatically at default levels.

And here we are... our server is fully configured. Go ahead, create your own app and deploy it. Comments on how to improve the above procedure will be appreciated.

Resources

comments powered by Disqus