Ninjas on a Penny Farthing

  posted  
Use Twitter? Follow on Twitter.

A Guide to a Nginx, Passenger and RVM Server


Please Note: This guide is moderately outdated at this point and will be rewritten in the near future. Until then, I suggest starting at the RVM site.

Following on from my previous post on the subject, today I’m going to present a step by step process to setting up a server (specifically, Ubuntu, but the technique is applicable to any linux server), rvm, nginx and passenger.

Before you get started

Most of the initial setup stage will be performed as root – if you don’t wish to use root directly (namely, for the initial rvm setup and most apt-get install instructions), make sure you prepend them with sudo as needed. If you don’t already have sudo, install it on your system (e.g. log in as root and apt-get install sudo on Ubuntu).

I’ll briefly cover setting up mysql and common dependencies for for some common rubygems but these sections are relatively specific to the distribution of choice (in this case, Ubuntu).

Lastly, please note that you’ll still only be able to use a single ruby interpreter with passenger – you can switch gemsets automatically on that interpreter but if you wish to have multiple rubies (e.g. app a on 1.8.7, app b on 1.9.2 and app c on jruby), you’ll need to look into an alternative setup using an app server like unicorn.

Step 1. Setting up the System

The first thing you’ll need is a server – In this case, I’ve chosen to start with a blank Ubuntu 10.04 server from the great guys over at Linode (if you want to sign up for them with my referral link, click here). However you decide to start, we’ll expect that for the rest of this guide you have root access to the machine you’ll be using.

To be able to install rvm, we’re need a couple of dependencies. Namely,

  • git – To clone from the rvm repository. On Ubuntu, you can get this with sudo apt-get install git-core.
  • curl – To get the rvm install system wide. On Ubuntu, you can get this with sudo apt-get install curl.

In my case, after booting the Ubuntu vps, I logged in as root and ran the following:

apt-get install curl git-core

Step 2. Getting RVM Running

While still logged in as root, we need to actually install rvm for all users. To this, we’ll follow a relatively straight forward process. First, we need to run a script (provided with rvm) that clones rvm and sets it up in a system wide place (namely, /usr/local/rvm). So, as root, run:

bash < <(curl -L http://bit.ly/rvm-install-system-wide)

Along side installing rvm itself in /usr/local/rvm, the above script will create two extra files to make the process work for us:

  • /etc/rvmrc – Tells rvm to run in a non-sandboxed mode, installed under /usr/local/rvm.
  • /usr/local/lib/rvm – a simple shell script to intelligently load rvm.

Also, since the system wide install relies using groups, it created an rvm group and added root to it. Once that was done, it made sure the files mentioned above and rvm itself were owned by the group and set a special bit on the directories to tell the operating system to preserve the group when making new files.

With that done, we now need edit the .bashrc for root and then the skeleton .bashrc so that when we add new users, rvm will be loaded correctly if they are members of the aforementioned rvm group. Armed with your editor of choice (and still logged in as root),

  • Open /root/.bashrc and /etc/skel/.bashrc.
  • First, find the line with [ -z "$PS1" ] && return, replacing it with if [[ -n "$PS1" ]]; then
  • Secondly, go to the very end of the file, adding:
fi
if groups | grep -q rvm ; then
  source "/usr/local/lib/rvm"
fi

If you couldn’t find a line with the && return as above, simply add the given if statement to the bottom of the two files.

The first set of changes (changing the && return) prevents the bash script from returning prematurely and second tells the system that if the current user is in the ‘rvm’ group when loading the script, it load rvm as a function.

Please note that if you already have users who you wish to let use rvm, you’ll need to also manually edit their profiles to add the if statement. Lastly, you’ll likely need to log out back in again for the script to pick up that you’re a member of the rvm group and to load it again.

Step 3. Adding Users

Now that you’ve setup the skeleton .bashrc to automatically load rvm, When we add users they’ll automatically have code to load rvm if they’re in the rvm group. In this case, we want to add a test user called darcy so we run the following:

# Add the user to the system
adduser darcy
# Add the user to the rvm group
adduser darcy rvm

You can verify all went well but opening a new shell / ssh session as the user you added (in this case, darcy) and verifying rvm is loaded as a function by running

type rvm | head -n1

This should show rvm is a function. If it doesn’t then either the edited .bashrc was incorrect or the user wasn’t correctly added to the rvm group.

Step 4. Setting up Rubies

Now that we’ve setup users and rvm, we’re going to need to actually install some rubies. For the current version of passenger, you’re restricted to one version of Ruby. In todays case, we’ll be using ruby enterprise edition.

But, before we get started, we’re going to tell rvm about some gems we want automatically installed into the @global gemset whenever install a new ruby. In this case, we’ll add rdoc and awesome_print by running the following bash whilst logged in as root or a user who is a member of the rvm group (e.g. darcy):

for gem in rdoc awesome_print; do
  echo $gem >> /usr/local/rvm/gemsets/global.gems
done

Next, we need to install the dependencies for the rubies we want to install. In most cases, this includes things like readline, bison, gcc and other such things required for compilation. In general, running rvm notes whilst logged in as root should give you the correct list. In the case of Ubuntu at the very moment, we need to run the following to install the dependencies for any MRI-based ruby:

aptitude install build-essential bison openssl libreadline5 libreadline-dev \
curl git-core zlib1g zlib1g-dev libssl-dev vim libsqlite3-0 libsqlite3-dev \
sqlite3 libreadline-dev libxml2-dev git-core subversion autoconf

Lastly, we install the ruby we actually want – In this case, ree. To do this, as either root or a user in the rvm group, we run

rvm install ree

We can then confirm this it installed and set it as the default for all users by running:

rvm use ree --default

And, to verify the extra gems were installed:

gem list

As a side note, By setting it as the default, we’ve also /usr/local/bin/ruby and similar files that make ruby generally available in PATH.

Step 5. Setting up Passenger and Nginx

Now that we have rvm and our ruby of choice installed, it’s time to set up passenger and nginx. Thanks to the great installer by the Phusion team, this process is rather straight forward. As root / any user in the rvm group (as usual), we’ll run:

  # Make sure we have the right ruby selected
  rvm use ree
  # Install the passenger gem
  gem install passenger
  # Run the passenger installer
  rvmsudo passenger-install-nginx-module

Answer the questions nginx asks of you,

For this to work with rvm, we need to change them slightly to point to a wrapper ruby executable. To get the path for the new passenger_ruby, as root / a user in the rvm group we run:

rvm use ree --passenger
which passenger_ruby

With the value from the second command (it should be /usr/local/bin/passenger_ruby), we edit the passenger_ruby option in the nginx config. By doing so, we provide a wrapper around ruby that makes sure we have the correct environment (e.g. if we setup passenger with a specific gemset, it tells passenger where to find the gems). To do this, whilst logged in as root you need to find the nginx config for passenger – in our case, it’s /opt/nginx/config/nginx.conf. Opening the file, we simply find the line similar to:

passenger_ruby /usr/local/rvm/rubies/ree-1.8.7-2010.02/bin/ruby;

And replace it with our new line:

passenger_ruby /usr/local/bin/passenger_ruby;

With the config file setup, in our specific case we’re going to tell Ubuntu how to start nginx at boot as well giving us a handy way to manage nginx. Whilst logged in as root, we’ll run:

curl -L http://bit.ly/nginx-ubuntu-init-file > /etc/init.d/nginx
chmod +x /etc/init.d/nginx
update-rc.d nginx defaults
/etc/init.d/nginx start

With that said and done, you should now have passenger and nginx running and hooked up to your rvm-managed ree. Also, you have a handy script at /etc/init.d/nginx you can use to manage nginx / restart it.

Step 6. Deploying an App on Passenger

Now that we’ve actually set up passenger, we need to set up our applications to deploy them with passenger. This really comes down to personal taste, especially concerning where apps are placed, but ultimately you just need to keep a few things in mind:

  1. Try and use a one user per application – it keeps things clean and simple
  2. Make sure said user is part of the rvm group (see Step 3)
  3. Where possible, use a rvmrc with a project-specific gemset for your app but make sure it uses the same ruby interpreter as passenger. This should be setup as part of your deploy or in your repo.
  4. When using .rvmrc’s, drop in our config/setup_load_paths.rb to make passenger automatically set your gemset for the applications.

For 4, in most cases this is a simple matter of changing into your application root and running:

mkdir -p config
curl -L http://bit.ly/rvm-passenger-slp > config/setup_load_paths.rb

And finally, edit config/setup_load_paths.rb so that it has only the bundler part that is relevant to you as noted by the comments inside it (by default, it has sections for both bundler 0.9 and 1.0).

Once you add and configure your app with nginx, passenger should automatically pick up this file and use it to configure your gemset etc on the fly to point to the correct location.

For more information on this specific setup, make sure you read the deployment best practices page on the rvm site.

Step 7. Setting up Capistrano

Now that you’ve set up your deployment server to use passenger, it’s very likely you’ll want to then setup your capistrano configurations to use the correct ruby. For the most part, using the above setup it should be as simple as adding the following to the top of your Capfile in your applications root:

# Add RVM's lib directory to the load path.
$:.unshift(File.expand_path('./lib', ENV['rvm_path']))
# Load RVM's capistrano plugin.
require "rvm/capistrano"
# Set it to the ruby + gemset of your app, e.g:
set :rvm_ruby_string, 'ree@something'

When you deploy, Capistrano should automatically pick up and use the correct ruby in the context of your scripts.

Step 8. Integrating other Utilities

With all that said and done, you should now have nginx, passenger, capistrano and the like set up correct but you may be wondering about other utilities (e.g. God). In general, your best best bet is to read the integration pages on the rvm site but if you can’t find anything specific there are a few simple rules of thumb that will make life easier:

  1. Make sure what ever user runs something is in the rvm group
  2. When starting an item in a non-bash environment (e.g. an init file), use the rvm wrapper tools (see rvm help wrapper) to generate an executable locked to the correct ruby environment.

Summary

Having done all of that, you should now have a system that serves as a good starting point for deploying applications under passenger and rvm. If you’re lucky, you may have also learnt a trick or two (e.g. rvm wrapper) that make doing certain tasks with rvm easier in general.

If you have any questions, I suggest reading through the rvm site and if you’re still unsure, ask me (Sutto) or wayneeseguin on IRC for help – we’re usually around in #rvm on freenode.

Obligatory Ruby Summer of Code Reference

For those following along with my Ruby Summer of Code progress, this is one of the final blog documenting my work as part of Ruby Summer of Code. In the next few days, I’ll be posting a wrap up blog post about what I’ve done so far and my experiences in general so make sure you check back.