Michael Crosby


Setup Ruby on Rails with Nginx and Unicorn

I lost an entire day of coding trying to get his to work. We are all lucky that I am a developer and not a systems admin.

However around 9pm I finally have it running. I burned through a lot of screwed up VM's trying to get this to work but finally made it. I tried Apache, Nginx, Passenger, and finally Nginx and Unicorn worked like it should. I don't know what the issues were but the actual rails app was not being ran.

I'll try to go through every single step I make, from installing Debian and the settings that I use to the installing and compiling gems, rails, and Nginx. I will also try to make this into a bash script to automate much of the install incase you f something up.

Requirements

  • Email
  • Rails
  • MySQL
  • Nginx

Installing Debian 6

I am going to use Debain 6 ( net install ) because it has the bare minimum. Only what we install is what it is going to have. You can also use Ubuntu Server 10.04 or 11.x with these commands. They should work the same. I have tested this on Ubuntu Server 10.04 so I know it works.

Ok boot into your linux install and configure your languages. I set the hostname as:

nginx-rails-server

Hostname

The domain name to my url.

Create your root and user passwords then for the disk partitioning just use the entire hard drive. I also choose to keep all system and user files on one partition.

Pick one of the package servers for debian. It does not matter.

Install Package Selection

Here is my package selection for install. We do not want a webserver or anything else. I usually install the Mail server so that I don't have to mess with it but since we are going to use Nginx and install mySQL manually all we want is the SSH and Mail servers installed right now.

Packages

Install GRUB boot load and restart.

Once your booted up, login really quick and run ifconfig to get the ip of the machine. We will want to SSH into it so we can use our desktop Terminal ( copy and paste ) instead of using a keyboard on the actual machine or through the VM.

Once you have the IP login as root via ssh. Lets nano and paste in the setup script from below. Chmod 755 it and let it rip.

Application Install

Setup Script

So now we need to get our base applications installed on the server. If you are using Ubutnu Server you will want to run:

sudo apt-get update
sudo apt-get upgrade

Also from here on out I will not place sudo infront of all the commands that need it. If you don't konw when you need to run the command with sudo or when not to, just go home. Don't waste my time emailing why when you run a command on Ubuntu it says "permission denied."

Here is the script that I will run to get the base apps installed from apt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#!/bin/bash

apt-get update
apt-get upgrade -y


apt-get install wget build-essential ruby1.8 ruby1.8-dev irb1.8 rdoc1.8 zlib1g-dev libopenssl-ruby1.8 libopenssl-ruby libzlib-ruby libssl-dev libpcre3-dev -y

apt-get install vim -y
apt-get install git-core -y
apt-get install screen -y
apt-get install ctags -y
apt-get install curl -y
apt-get install libsqlite3-dev -y
apt-get install libcurl4-openssl-dev -y
apt-get install libssl-dev -y

If you notice I did not install mySQL in here because it will ask for the root passwords and I wanted this base install script to be fully automatic.

Now that that is done lets go ahead and isntall mySQL

apt-get install mysql-server libmysqlclient16 libmysqlclient-dev mysql-client mysql-common -y

When asked, pick a good mySQL root password.

Gems

Now that we have everything from apt installed lets get rubygems installed. I don't want to install gems via apt so we are going to grab the tarbal and install that way.

cd /opt/
wget http://production.cf.rubygems.org/rubygems/rubygems-1.5.1.tgz
tar -zxvf rubygems-1.5.1.tgz
cd /opt/rubygems-1.5.1/
ruby setup.rb

This will install gems in:

/usr/bin/gem1.8

Logout of your bash session and them back in and you should be able to type gem1.8 to execute some commands.

Create another bash setup script for all these gem installs. They will take a while so it's easier to just let it roll. I chose not to install the rdoc and ri because this is the server and we don't need it.

1
2
3
4
5
6
7
8
#!/bin/bash
gem1.8 install fastthread --no-rdoc --no-ri 
gem1.8 install rake rack --no-rdoc --no-ri 
gem1.8 install rails --no-rdoc --no-ri 
gem1.8 install mysql --no-rdoc --no-ri 
gem1.8 install unicorn --no-rdoc --no-ri 
gem1.8 install sqlite3-ruby --no-rdoc --no-ri 
gem1.8 install execjs --no-rdoc --no-ri

Javascript engine

Rails needs a javascript engine to run. Pick your favorite one and install it. I will isntall node.js for this. I will not use apt or gems to install node.js but will grab the latest stable release from github and compile it.

Lets grab the latest stable release from github and download it in our /opt/ dir.

wget -O nodejs.zip https://github.com/joyent/node/zipball/v0.6.11

One thing that I forgot to install is zip so that we can unzip this file. So quickly install

apt-get install zip

then unzip node.

unzip nodejs.zip

Lets cd into joyent-node-* folder and start building it

./configure
make
make install

Also make sure that you install coffee-rails and jquery-rails. I got this error when trying to make the test app below. It may be a good idea to make a test app as root so bundler to handle these for us.

gem1.8 install coffee-rails --no-rdoc --no-ri
gem1.8 install jquery-rails --no-rdoc --no-ri

Error

Testing rails

Now that we have everything setup lets login to our user account ( not root ) and create a sample rails app and run it with the builtin WEBrick server to make sure that rails works on our server.

In your home dir:

rails new testRails
cd testRails/
rails server

Webrick

Your server should start up fine so navigate to it's ip and make sure that rails is running and the environment links displays information about your setup.

Test App

Hopefully your rails test app looks like this in the browser. If not then don't even try to go forward. Get the WEBrick server working first and then move on.

Nginx

We will go ahead and install nginx via the apt repo. We first need to add the key to the resposity so download it then add it.

wget http://nginx.org/keys/nginx_signing.key
apt-key add nginx_signing.key

Now add the repos to your /etc/apt/sources.list

deb http://nginx.org/packages/debian/ squeeze nginx
deb-src http://nginx.org/packages/debian/ squeeze nginx

Then

apt-get update
apt-get install nginx

It's a really quick install. After the install is finished you can test to make sure that it works by going to your machines ip address. You should see the welcome to nginx text.

User group and dir

Now lets setup a new directory that nginx and our rails apps will be in.

cd /var
mkdir www
usermod -a -G www-data nginx
usermod -a -G www-data YOURUSERNAME

chgrp -R www-data www/
chmod -R 775 www/

Now our user and nginx should be part of www-data group and own /var/www

Nginx Config

Now lets grab the config file form the unicorn repo and place it in the nginx config directory overwriting the default config.

cd /etc/nginx/
cp nginx.conf nginx.conf.old
rm nginx.conf
wget https://raw.github.com/defunkt/unicorn/master/examples/nginx.conf

Now we need to setup our paths in the nginx.conf file.

Uncomment this line:

listen 80 default deferred; # for Linux

Change the root paths to there our apps public dir is:

root /var/www/testapp/public;

Save and restart nginx

/etc/init.d/nginx restart

Now lets get unicorn setup with init.d and make a test app in the www dir via our other user.

Create the test app

Login as your normal user and create a new app called "testapp" in /var/www

rails new testapp

Everything should go smooth if you have things setup right. You don't need root or sudo to make the test app in var/www because we added ourself and nginx to the group that owns the folder. Lets cd into the test app's config dir and download the unicorn config file.

cd testapp/config/
wget -O unicorn.rb https://raw.github.com/defunkt/unicorn/master/examples/unicorn.conf.rb

Vim or nano into it and setup the paths to your app. Here is my config:

# Sample verbose configuration file for Unicorn (not Rack)
#
# This configuration file documents many features of Unicorn
# that may not be needed for some applications. See
# http://unicorn.bogomips.org/examples/unicorn.conf.minimal.rb
# for a much simpler configuration file.
#
# See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete
# documentation.

# Use at least one worker per core if you're on a dedicated server,
# more will usually help for _short_ waits on databases/caches.
worker_processes 4

# Since Unicorn is never exposed to outside clients, it does not need to
# run on the standard HTTP port (80), there is no reason to start Unicorn
# as root unless it's from system init scripts.
# If running the master process as root and the workers as an unprivileged
# user, do this to switch euid/egid in the workers (also chowns logs):
# user "unprivileged_user", "unprivileged_group"

# Help ensure your application will always spawn in the symlinked
# "current" directory that Capistrano sets up.
working_directory "/var/www/testapp" # available in 0.94.0+

# listen on both a Unix domain socket and a TCP port,
# we use a shorter backlog for quicker failover when busy
listen "/tmp/.sock", :backlog => 64
listen 8080, :tcp_nopush => true

# nuke workers after 30 seconds instead of 60 seconds (the default)
timeout 30

# feel free to point this anywhere accessible on the filesystem
pid "/var/www/testapp/shared/pids/unicorn.pid"

# By default, the Unicorn logger will write to stderr.
# Additionally, ome applications/frameworks log to stderr or stdout,
# so prevent them from going to /dev/null when daemonized here:
stderr_path "/var/www/testapp/shared/log/unicorn.stderr.log"
stdout_path "/var/www/testapp/shared/log/unicorn.stdout.log"

# combine Ruby 2.0.0dev or REE with "preload_app true" for memory savings
# http://rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
preload_app true
GC.respond_to?(:copy_on_write_friendly=) and
  GC.copy_on_write_friendly = true

before_fork do |server, worker|
  # the following is highly recomended for Rails + "preload_app true"
  # as there's no need for the master process to hold a connection
  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.connection.disconnect!

  # The following is only recommended for memory/DB-constrained
  # installations.  It is not needed if your system can house
  # twice as many worker_processes as you have configured.
  #
  # # This allows a new master process to incrementally
  # # phase out the old master process with SIGTTOU to avoid a
  # # thundering herd (especially in the "preload_app false" case)
  # # when doing a transparent upgrade.  The last worker spawned
  # # will then kill off the old master process with a SIGQUIT.
  # old_pid = "#{server.config[:pid]}.oldbin"
  # if old_pid != server.pid
  #   begin
  #     sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
  #     Process.kill(sig, File.read(old_pid).to_i)
  #   rescue Errno::ENOENT, Errno::ESRCH
  #   end
  # end
  #
  # Throttle the master from forking too quickly by sleeping.  Due
  # to the implementation of standard Unix signal handlers, this
  # helps (but does not completely) prevent identical, repeated signals
  # from being lost when the receiving process is busy.
  # sleep 1
end


after_fork do |server, worker|
  # per-process listener ports for debugging/admin/migrations
  # addr = "127.0.0.1:#{9293 + worker.nr}"
  # server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true)

  # the following is *required* for Rails + "preload_app true",
  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.establish_connection

  # if preload_app is true, then you may also want to check and
  # restart any other shared sockets/descriptors such as Memcached,
  # and Redis.  TokyoCabinet file handles are safe to reuse
  # between any number of forked children (assuming your kernel
  # correctly implements pread()/pwrite() system calls)
end

They are easily found and just point them to your app dir.

![Unicorn Config][8]

Now lets run unicorn and test out our app. Make two new folders in your app before login back in as root and running unicorn.

mkdir /shared
cd shared/
mkdir pids
mkdir log

Login as root to run unicorn:

unicorn_rails -c /var/www/testapp/config/unicorn.rb -D

Now go to your server ip in your browser and you should see the same thing with the ability to click the envirionment link with data displayed. If you do no see any data then unicorn is not running or setup correctly. If you do see it then we are done. It's working and ready to go. You may want to add unicorn to an init.d script so that it starts with your server.

Unicorn init.d

As root:

cd /etc/init.d/
vim unicorn

Here is the init.d script that I am using so just paste it into the file.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#! /bin/sh

# File: /etc/init.d/unicorn

### BEGIN INIT INFO
# Provides:          unicorn
# Required-Start:    $local_fs $remote_fs $network $syslog
# Required-Stop:     $local_fs $remote_fs $network $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: starts the unicorn web server
# Description:       starts unicorn
### END INIT INFO

DAEMON=/usr/bin/unicorn
DAEMON_OPTS="-c /var/www/testapp/config/unicorn.rb -D"
NAME=unicorn
DESC="Unicorn app for testapp"
PID=/var/web/testapp/shared/pid/unicorn.pid

case "$1" in
  start)
    echo -n "Starting $DESC: "
    $DAEMON $DAEMON_OPTS
    echo "$NAME."
    ;;
  stop)
    echo -n "Stopping $DESC: "
        kill -QUIT `cat $PID`
    echo "$NAME."
    ;;
  restart)
    echo -n "Restarting $DESC: "
        kill -QUIT `cat $PID`
    sleep 1
    $DAEMON $DAEMON_OPTS
    echo "$NAME."
    ;;
  reload)
        echo -n "Reloading $DESC configuration: "
        kill -HUP `cat $PID`
        echo "$NAME."
        ;;
  *)
    echo "Usage: $NAME {start|stop|restart|reload}" >&2
    exit 1
    ;;
esac

exit 0

Edit to your correct paths then:

chmod +x unicorn
/usr/sbin/update-rc.d -f unicorn defaults

Reboot and see if nginx and unicorn are successfully servering your app.

Everything should work on the defult page via port 80. If you do not see images or the environment info that means unicorn is not running. Check the log files that we setup in the config ( the paths ) and see what is happening. I hope this tutorial works for everyone. I had a lot of trouble setting this up so hopefully you will not repeat my pain.

comments powered by Disqus