Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Ruby on Rails server setup on Ubuntu 11.04 with Nginx, Unicorn, Rbenv
#! /bin/bash
### 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
USER=example.co.uk
PATH=/home/$USER/.rbenv/bin:/home/$USER/.rbenv/shims:$PATH
DAEMON=unicorn
DAEMON_OPTS="-c /home/$USER/website/config/unicorn.rb -E production -D"
NAME=unicorn
DESC="Unicorn app for $USER"
PID=/home/$USER/website/tmp/pids/unicorn.pid
case "$1" in
start)
CD_TO_APP_DIR="cd /home/$USER/website"
START_DAEMON_PROCESS="bundle exec $DAEMON $DAEMON_OPTS"
echo -n "Starting $DESC: "
if [ `whoami` = root ]; then
su - $USER -c "$CD_TO_APP_DIR > /dev/null 2>&1 && $START_DAEMON_PROCESS"
else
$CD_TO_APP_DIR > /dev/null 2>&1 && $START_DAEMON_PROCESS
fi
echo "$NAME."
;;
stop)
echo -n "Stopping $DESC: "
kill -QUIT `cat $PID`
echo "$NAME."
;;
restart)
echo -n "Restarting $DESC: "
kill -USR2 `cat $PID`
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
upstream example-workers {
# fail_timeout=0 means we always retry an upstream even if it failed
# to return a good HTTP response (in case the Unicorn master nukes a single worker for timing out).
server unix:/tmp/example.co.uk.socket fail_timeout=0;
}
server {
listen 80; # default;
server_name example.co.uk;
root /home/example.co.uk/website/public;
location / {
access_log off;
include proxy_params;
proxy_redirect off;
if (-f $request_filename) {
access_log off;
expires max;
break;
}
if (-f $request_filename.html) {
rewrite (.*) $1.html break;
}
if (!-f $request_filename) {
proxy_pass http://example-workers;
break;
}
}
}

Server Commissioning

Ubuntu mainstream packages are pretty out of date for nginx; we want version > 1.0, so we need to reference repository that has more recent versions before we install:

$ add-apt-repository ppa:nginx/stable && apt-get update

Update, upgrade and install nginx and development tools:

$ apt-get -y install nginx git-core build-essential

Extras for RubyGems and Rails:

$ apt-get -y install zlib1g-dev
$ apt-get -y install libssl-dev libsqlite3-dev
$ apt-get -y install libreadline5-dev
$ apt-get -y install curl

Add a deployment user:

$ useradd -m -g staff -s /bin/bash deployer
$ passwd deployer

Create a custom shudders file, and add the following line (sudo vi /etc/sudoers.d/our-company):

%staff ALL=(ALL) ALL

Nginx

Edit /etc/nginx/proxy_params and add shared proxy config settings (optional)

proxy_set_header Host $host;

# needed to forward user's IP address to application server
proxy_set_header X-Real-IP $remote_addr;

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header   X-Forwarded-Proto $scheme;

proxy_send_timeout         90;
proxy_read_timeout         90;

proxy_buffer_size          4k;
proxy_buffers              4 32k;
proxy_busy_buffers_size    64k;
proxy_temp_file_write_size 64k;

Ruby & Rails Setup ror each User / App

$ sudo adduser --shell /bin/bash example-user
$ su - example-user
$ cd ~example-user

Install rbenv:

Check out rbenv into ~/.rbenv :


$ git clone git://github.com/sstephenson/rbenv.git .rbenv

Add ~/.rbenv/bin to your $PATH for access to the rbenv command-line
:

$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >>   
$  ~/.bash_profile

Add rbenv init to your shell to enable shims and autocompletion:


$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile

Restart your shell so the path changes take effect in order to use rbenv:


$ exec $SHELL

If the above shell reload doesn't give you the rbenv command, then you will have to exit and re-enter the shell

Use rbenv to install a specific Ruby version
:

$ rbenv install 1.9.2-p290

Rebuild the shim binaries. You should do this any time you install a new Ruby binary e.g. when installing a new Ruby version, or when installing a gem that provides a binary:

$ rbenv rehash

Set a global Ruby version for all shells:

$ rbenv global 1.9.2-p290

Unicorn

$ gem install bundler unicorn --no-rdoc --no-ri; rbenv rehash

Add the following environment config variables to a file at /etc/unicorn/example.co.uk.conf (The Unicorn process will look here):

RAILS_ROOT=/home/example.co.uk/website
RAILS_ENV=production

Clone your app into ~/website (depends upon if you are using git, or some other source), and then install your bundle:

$ cd ~/website
$ bundle install

create app-specific unicorn init file here and make it executable (See init file in this gist):

$ sudo chmod +x /etc/init.d/unicorn_example.co.uk  

Add a unicorn.rb app config file to your Rails app at config/unicorn.rb (Example attached). Edit the file to match the directory path to your app, and user names and groups. Also uncomment the 'listen' directive in the file for listening on a TCP port.

Check that unicorn can be started:

$ /etc/init.d/unicorn_example.co.uk start

Check that unicorn is listening on the configured port (8080 in this example):

$ netstat -natp | grep unicorn

You should see something like:

Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN      5272/unicorn.rb -E 

Okay, if that's all good, then you can comment out the TCP port listening directive in config/unicorn.rb, so that unicorn worker processes are only accessible to Nginx via Unix socket.

Nginx virtual hosts

Create an Nginx virtual hosts configuration file in 'sites-available' and enter contents of nginx_virtual_host file:

$ sudo vi /etc/nginx/sites-available/example.co.uk

Create a symlink from sites-available to site-enabled:

$ sudo ln -s /etc/nginx/sites-available/example.co.uk /etc/nginx/sites-enabled/example.co.uk
# See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete documentation
#
# This file should go in the config directory of your Rails app e.g. config/unicorn.rb
app_dir = "/home/example.co.uk/website/"
worker_processes 5
working_directory app_dir
# Load app into the master before forking workers for super-fast
# worker spawn times
preload_app true
# nuke workers after 60 seconds (the default)
timeout 60
# listen on a Unix domain socket and/or a TCP port,
#listen 8080 # listen to port 8080 on all TCP interfaces
#listen "127.0.0.1:8080" # listen to port 8080 on the loopback interface
listen "/tmp/example.co.uk.socket"
# feel free to point this anywhere accessible on the filesystem
user 'example.co.uk', 'example.co.uk'
pid "#{app_dir}/pids/unicorn.pid"
stderr_path "#{app_dir}/log/unicorn.stderr.log"
stdout_path "#{app_dir}/log/unicorn.stdout.log"
# http://www.rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
if GC.respond_to?(:copy_on_write_friendly=)
GC.copy_on_write_friendly = true
end
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!
##
# When sent a USR2, Unicorn will suffix its pidfile with .oldbin and
# immediately start loading up a new version of itself (loaded with a new
# version of our app). When this new Unicorn is completely loaded
# it will begin spawning workers. The first worker spawned will check to
# see if an .oldbin pidfile exists. If so, this means we've just booted up
# a new Unicorn and need to tell the old one that it can now die. To do so
# we send it a QUIT.
#
# Using this method we get 0 downtime deploys.
old_pid = "#{server.config[:pid]}.oldbin"
if File.exists?(old_pid) && server.pid != old_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
# someone else did our job for us
end
end
end
after_fork do |server, worker|
# Unicorn master loads the app then forks off workers - because of the way
# Unix forking works, we need to make sure we aren't using any of the parent's
# sockets, e.g. db connection
defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
# Redis and Memcached would go here but their connections are established
# on demand, so the master never opens a socket
end
@vsizov

This comment has been minimized.

Copy link

@vsizov vsizov commented Mar 7, 2012

root@tool:~# /etc/init.d/redmine start
Starting Unicorn app for redmine: -su: bundle: command not found
unicorn.

;(

but if run script from user redmine then work fine

@inspire22

This comment has been minimized.

Copy link

@inspire22 inspire22 commented Jan 28, 2014

Great init.d script for unicorn with bundle exec. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.