@awead

Technical notes and explications of code

Using Rbenv in Production

In the last post, I went over the procedures I used to install the Ruby environment manager, rbenv, and setup it for developing rails applications.

In this post, I’ll cover how I use rbenv on my production and test servers for deploying rails applications using Apache with Passenger.

The Environment

  • CentOS 6
  • ruby 1.9.3-p286
  • apache
  • passenger 4.0.5

Requirements

You’ll need development packages (gcc, etc.) and git. Easiest way to do that is install the development tools package:

$ yum groupinstall "Development Tools"

I installed git via yum as well, but by adding the Extra Packages for Enterprise Linux (EPEL) repository:

$ rpm -Uvh http://download.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm
$ yum install git

Installation

This Rbenv wiki page recommends creating a user under which your applications are run and Rbenv would be installed under this user’s home directory. This certainly is a fine way to do it, but I want to have a global ruby available system-wide to any user. I chose to install Rbenv under /usr/local, my default location for any non-repo software. You could also choose /opt if you like. I also prefer to not have rbenv in a hidden folder. That way, when I list the contents of /usr/local, I can immediately see what software I have installed outside of Red Hat’s or CentOS’s normal repository.

Other than the location, the installation is pretty much the same as it was in development:

(as root)
$ cd /usr/local
$ git clone https://github.com/sstephenson/rbenv
$ cd rbenv
$ mkdir plugins
$ cd plugins
$ git clone https://github.com/sstephenson/ruby-build
$ git clone https://github.com/sstephenson/rbenv-vars

Because I want rbenv available globally, I add a new file under /etc/profile.d so new environment settings will be applied equally to every user:

/etc/profile.d/rbenv.sh
1
2
3
export RBENV_ROOT=/usr/local/rbenv
export PATH="$RBENV_ROOT/bin:$PATH"
eval "$(rbenv init -)"

After logging out and back in again, or re-sourcing .bash_profile, Rbenv should now be installed and correctly pathed:

$ rbenv install 1.9.3-p286
$ rbenv global 1.9.3-p286
$ rbenv rehash

Configuration

First, we’ll clear out any unwanted gems, then install the ones we need to deploy applications. For these systems, I install just bundler and passenger. Additionally, I configure gem for root so that it does not install any documentation:

$ gem list | awk '{print $1}' | xargs gem uninstall -a
$ echo "gem: --no-rdoc --no-ri" >> ~/.gemrc
$ gem install bundler passenger
$ rbenv rehash

Now we should have passenger and bundler avaialable from anywhere in the system, but we have to configure bundler to install any other gems locally to the application, and have rbenv-vars set the GEM_HOME variable accordingly:

$ bundle config --global path .bundle
$ bundle config --global bin bin
$ echo "GEM_HOME=.bundle" >> ~/.rbenv/vars
$ rbenv rehash

Now, when we run bundle install on any of our rails apps, additional gems will be installed the .bundle directory under the application.

Passenger

The last little bit involves configuring Passenger. Installing the apache module is straight-forward:

$ passenger-install-apache2-module

But when I configure my rails apps for deployment, I have apache set the GEM_HOME variable so Passenger knows to look under the .bundle directoryfor the additional gems. This can be done so that it’s set for any application you deploy:

/etc/httpd/conf.d/passenger.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<VirtualHost *:80>
  ServerName my.prod.server
  SetEnv GEM_HOME .bundle

  RackBaseURI /app1
  <Directory /var/www/html/app1>
    Options -MultiViews
  </Directory>

  RackBaseURI /app2
  <Directory /var/www/html/app2>
    Options -MultiViews
  </Directory>
</VirtualHost>

Acknowledgements

http://www.vxnick.com/blog/2012/04/setting-up-rbenv-globally/

Moving to Rbenv

I’ve been a loyal RVM user for several years now, but recently I grew a bit tired of using it in production. It seemed too heavy-handed and updating was arduous. After some looking around, I settled on rbenv, mostly because it was simpler, smaller and could work well in all my environments: from my laptop, where I develop, to the different servers on which I deploy everything. Keeping it all identical across these is very important to me because I don’t want to have to re-think anything when releasing new code.

I’m not down on RVM at all… in fact, I still like it very much and if I were just developing and not involved in the production side of things, I’d probably still be using RVM. One of the particularly great things about RVM is its notion of gemsets, or groups of installed gems that can be used for one particular application. RVM also has default and global gemsets, which I liked to use for gems that were required for all applications. I liked to use this in my production servers where I could deploy all my applications with the same passenger gem.

As it turns out, we can use a properly-configured bundler to mimic an RVM gemset, and use the unadorned gem command to install gems that we want global to our system. Doing all of this and switching to rbenv took a little adjustment, but overall I’m very happy with it. Getting to that point required some research and some experimenting, which I’ll go over here.

Overview

Here’s the basic gist of my setup:

  • rbenv, with the ruby-build and rbenv-vars plugins, installs and manages your ruby versions, as well as sets some environment variables
  • gem installs rubies that are global to your system, akin to rvm’s global gemsets
  • bundler install gems specific to a project or application, akin to rvm’s named gemsets
  • tie it all together with environment variables

Development

First, you’ll need to make sure you have these installed:

On OSX, rbenv installation is a snap with Homebrew. Follow the instructions to install rbenv on Mac:

https://github.com/sstephenson/rbenv#homebrew-on-mac-os-x

Additionally, you’ll need the rbenv-vars plugin. I installed that manually:

$ cd ~/.rbenv/plugins
$ git clone https://github.com/sstephenson/rbenv-vars
$ rbenv rehash

Install your ruby of choice and make that global:

$ rbenv install 1.9.3-p286
$ rbenv global 1.9.3-p286
$ rbenv rehash

When rbenv installs ruby, it also installs some gems:

$ gem list

Since it has installed these with the gem command, these are essentially system-wide or global gems. I prefer to have more control over which gems are installed where, so I remove these and then only install gems I need globally. Presently, the only gems I use everywhere are bundler and rails:

$ gem list | awk '{print $1}' | xargs gem uninstall -a
$ gem install bundler rails

Now we configure bundler to store project-specific gems in a local .bundle directory and to install any gem executeables in a local bin directory:

$ bundle config --global path .bundle
$ bundle config --global bin bin

Lastly, we need to make our environment aware that additional gems are installed in .bundle. For that, we can use the rbenv-vars command which will set any environment variable we need. In this case, we need to set GEM_HOME to our .bundle directory:

$ echo "GEM_HOME=.bundle" >> ~/.rbenv/vars
$ rbenv rehash
Example project

So, here’s how this all works. Let’s say I want to create a new rails application and then tweak its ruby version and gems. First, I’ll install a new, blank app, then I’ll install a new version of ruby, set up that application to use it and install some gems specific to that application:

$ cd ~/Projects
$ rails new sample_app
$ cd sample_app
$ rbenv install 2.0.0-p247
$ echo "2.0.0-p247" > .ruby-version
$ rbenv rehash
$ echo "gem 'rspec'" >> Gemfile
$ bundle install

This all should look familiar, except note the .ruby-version file. This file can be used in any directory to indicate which ruby version rbenv should use. When you set the global ruby version above, rbenv wrote that version to ~/.rbenv/version which means that unless there’s a version explicitly named in .ruby-version, rbenv defaults to the version specified in ~/.rbenv version.

Next, take a look around:

$ ls -a
$ ls bin
$ ls .bundle
$ ls .bundle/gems
$ ruby --version
$ gem list

You’ll see that rspec is installed in the .bundle/gems directory and the rspec command is in the bin directory. Listing the gems will return all the gems both system-wide and what’s installed locally, and you should be using ruby version 2. If we move to a different directory, we should be back to our global defauls:

$ cd ..
$ ruby version
$ gem list

Caveats

Paths

When running local gem executeables such as rspec, you’ll either need to call them using bin/rspec or use bundler:

$ bundle exec rspec

Alternatively, you could preface your PATH with the local bin directory, so it looks there first:

$ PATH=bin:$PATH; export PATH

Personally, I choose to just call local executables by prefacing them with bin/ — it takes some getting used to at first, but I prefer this method because it makes me realize exactly what I’m doing.

Rails 4

When using this setup with Rails 4 applications, rails complained about bundler setting the local bin directory. Apparently, rails is now defaulting to using bin locally. So if you want to develop multiple versions of rails, including version 4, I would remove the BUNDLE_BIN setting in your global ~/.bundle/config file and set it locally to your applications that need it.

My updated setup, for Rails 3 and Rails 4 develpment looks like this:

$ cat ~/.bundle/config 
  ---
  BUNDLE_PATH: .bundle
$ cd ~/Projects/rails3_app
$ bundle config --local bin bin
$ cat .bundle/config
  ---
  BUNDLE_DISABLE_SHARED_GEMS: '1'
  BUNDLE_BIN: bin
$ cd ~/Projects/rails4_app
$ cat .bundle/config
  ---
  BUNDLE_DISABLE_SHARED_GEMS: '1'

That about covers it. Next post, I’ll go over how to do this same setup on test and production deployment servers.

Acknowledgments

Most of this was me googling to see how other people did it, and then making it work for my own particular needs. If it wasn’t for them, I couldn’t have done it:

Sam Stephenson’s Rbenv

Devoh’s ‘Implict Gemsets’