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 withsudo apt-get install git-core. -
curl– To get the rvm install system wide. On Ubuntu, you can get this withsudo 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-coreStep 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/.bashrcand/etc/skel/.bashrc. - First, find the line with
[ -z "$PS1" ] && return, replacing it withif [[ -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"
fiIf 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 rvmYou 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 -n1This 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
doneNext, 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 autoconfLastly, 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 reeWe can then confirm this it installed and set it as the default for all users by running:
rvm use ree --defaultAnd, to verify the extra gems were installed:
gem listAs 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-moduleAnswer 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_rubyWith 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 startWith 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:
- Try and use a one user per application – it keeps things clean and simple
- Make sure said user is part of the rvm group (see Step 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.
- When using .rvmrc’s, drop in our
config/setup_load_paths.rbto 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.rbAnd 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:
- Make sure what ever user runs something is in the rvm group
- When starting an item in a non-bash environment (e.g. an init file), use the
rvm wrappertools (seervm 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.