RVM did its job well, but it always felt heavy handed and overly complex, especially for a server.
I heard about rbenv when it first started making the rounds in 2011, and it got my interest, but since I was using RVM locally and on the server, I wasn’t able to try it out without some destructive changes to my environments (since the two don’t play nice together).
A few weeks ago I was planning to update to Ruby 1.9.3 and Rails 3.2. Being a full-time developer, I don’t have as much time as I’d like to tend to the servers. However, I decided that since I was doing some spring cleaning anyway, I’d go ahead switch to rbenv, both locally and on the server.
The decision was not made lightly. After all, RVM was doing its job just fine. However, I never understood how RVM worked internally, other than that it did some magic by replacing the cd
command with a function. I took a little time trying to understand it, but I just couldn’t get there.
rbenv, on the other hand, is lightweight and very simple to understand. You add a couple entries to your $PATH
, and rbenv updates symlinks so that the active Ruby version is on your path without all the magic.
Here I’ll walk through the process step by step, showing the commands I entered and config changes I made to switch from RVM to rbenv.
First, remove RVM and all traces from the system. This is the scary part, because there’s no way to undo. Sure, you could reinstall RVM, but it will likely be a more up-to-date version than what you had.
I originally installed RVM on the server in multiuser mode, so I need to use rvmsudo
here. (sudo
wouldn’t work because rvm
was not on my $PATH
.) Running rvm implode
is used to uninstall RVM. In addition, you’ll need to remove some references manually that implode
doesn’t take care of.
# uninstall rvm and its supporting files
$ rvmsudo rvm implode
# remove rvmrc file for multiuser installs
$ sudo rm /etc/rvmrc
# remove the 'rvm' group
$ sudo delgroup rvm
# remove 'rvm' references from .bashrc, .bash_profile, .profile, etc.
$ vim ~/.bashrc
At this point, we need to start a new shell (e.g., a new ssh session) so that we’re completely rid of RVM (remember, RVM modified our cd
command, and uninstalling it won’t remove the effects of the command proxy until logging in again).
Next, follow the rbenv install instructions:
$ git clone git://github.com/sstephenson/rbenv.git ~/.rbenv
# add ~/.rbenv/bin to the $PATH and `rbenv init` to the shell
# (use ~/.profile instead of ~/.bash_profile on Ubuntu)
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
# start a new shell
$ exec $SHELL -l
Next, install ruby-build as a plugin to rbenv.
$ git clone git://github.com/sstephenson/ruby-build.git \
~/.rbenv/plugins/ruby-build
Now we can install Ruby.
$ rbenv install 1.9.3-p392
# rebuild the rbenv shims
$ rbenv rehash
# optionally use this new version as the global default
$ rbenv global 1.9.3-p392
# make sure the global setting stuck
$ which ruby
# => /home/admin/.rbenv/shims/ruby
$ ruby -v
# => ruby 1.9.3p392 (2013-02-22 revision 39386) [x86_64-linux]
After getting RVM removed and rbenv installed, I needed to update the Rails apps. To be on the safe side, I removed the bundle
directory for each app (where Bundler keeps the gems in a standard deployment with Capistrano) so I could install a fresh copy of the gems under the new version of Ruby (as I had just upgraded from 1.9.2 to 1.9.3).
And when I say ‘removed’, I mean that I moved the directories to a place the app wasn’t looking for them, just in case.
# install gem bundler
gem install bundler
rbenv rehash
$ mkdir -p ~/tmp/old-bundles
# for each app: back up old bundles and reinstall
$ mv ~/apps/myapp/shared/bundle ~/tmp/old-bundles/myapp
$ cd ~/apps/myapp/current
$ bundle install --path ~/apps/myapp/shared/bundle \
--deployment --quiet \
--without development test
# make sure the app can at least load in console mode
$ cd ~/apps/myapp/current
$ bundle exec rails console production
As with many apps, I have a few cron jobs that run rake tasks inside my app as well as a few Ruby scripts for backups and the like. These used RVM, so I needed to make some updates there as well.
# first, back up the existing crontab
mkdir -p ~/tmp/crontab
crontab -l > ~/tmp/crontab/2013-03-20.txt
Next, I removed references to my custom rvm-run
script and replaced them with a simple bash -lc
(to load a shell with rbenv configured) and added bundle exec
where necessary. (I plan to eventually use binstubs instead of having to prefix everything with bundle exec
, but that’s for another day.)
First, an example of a Rake task cron job, split onto multiple lines with a backslash for readability:
# the old way, with RVM and my rvm-run script:
0 0 * * * cd /home/admin/apps/myapp/current && \
/home/admin/scripts/rvm-run \
"bundle exec rake app:task RAILS_ENV=production"
# the new rbenv cron job:
0 0 * * * bash -lc \
"cd /home/admin/apps/myapp/current && \
bundle exec rake app:task RAILS_ENV=production"
Next, an example of a Ruby script cron job, also split for readability:
# the old way, with RVM and my rvm-run script:
5 0 * * * /home/admin/scripts/rvm-run \
"/home/admin/scripts/backup-databases"
# the new rbenv cron job:
5 0 * * * bash -lc \
"cd /home/admin/scripts && bundle exec backup-databases"
For the record, the Deploying with rbenv wiki page makes mention of how to manually set the $PATH
for cron jobs without the use of bash -lc
, but I couldn’t get it to work in the admittedly few minutes I tried.
It’s also worth mentioning that I initially had trouble with cron still having a reference to RVM, so I restarted the cron daemon, which on Ubuntu is done with:
sudo service cron restart
For the sake of completeness, my rvm-run
wrapper script is at the end of this post.
The init.d
scripts I use for starting, stopping, and reloading Unicorn also referenced RVM, so they needed some attention as well.
In the unicorn
script, I changed this:
CMD="rvm use 1.9.2@rails-3.0 && \
cd $APP_ROOT && \
rvmsudo bundle exec unicorn -c config/unicorn.rb -E $ENV -D"
to this:
PATH="/home/$USER/.rbenv/shims:/home/$USER/.rbenv/bin:$PATH"
CMD="cd $APP_ROOT; bundle exec unicorn -c config/unicorn.rb -E $ENV -D"
After updating the Unicorn scripts, I wanted to restart the apps under Ruby 1.9.3 and using the newly installed bundle. (Remember to make sure you can launch your app in console mode before this next step.)
Unfortunately, a zero-downtime reload of an app won’t work in this case because of all the changes. When I tried that (with sudo /etc/init.d/unicorn-myapp reload
), I received this error:
executing ["/home/admin/apps/myapp/shared/bundle/ruby/1.9.1/bin/unicorn",
"-c", "config/unicorn.rb", "-E", "production", "-D"]
(in /home/admin/apps/myapp/releases/20130301010101)
/usr/bin/env: ruby: No such file or directory
reaped #<Process::Status: pid 12345 exit 127> exec()-ed
To work around this, I stopped and then started each app:
sudo /etc/init.d/unicorn-myapp stop
sudo /etc/init.d/unicorn-myapp start
After that, I had the existing apps running under a new version of Ruby with rbenv instead of RVM.
The full unicorn
script is at the end of this post.
I use Capistrano to deploy my Rails apps, and because it needed to be configured for RVM, I had to make a few changes to get it working with rbenv.
The deploy.rb
file started like this with RVM:
# 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 the RVM environment on the server
set :rvm_ruby_string, '1.9.2@rails-3.0'
It now starts out like this:
# add binstubs in app/bin directory
set :bundle_flags, '--deployment --quiet --binstubs'
# set up the environment for rbenv since Capistrano
# doesn't execute .profile (so it needs rbenv's $PATH)
set :default_environment, {
'PATH' => "$HOME/.rbenv/shims:$HOME/.rbenv/bin:$PATH"
}
In addition, I had one more reference to RVM in the deploy:start
task that took care of starting Unicorn. What used to be this (split onto multiple lines for readability):
task :start, roles: :app, except: { no_release: true } do
# run a simple sudo command to force Capistrano to
# ask for the sudo password, because rvmsudo doesn't
# cause Capistrano to prompt for a password (this will
# let the next 'rvmsudo' command run)
sudo "date"
run "cd #{current_path} && rvmsudo bundle exec unicorn " +
"-c #{unicorn_config} -E #{rails_env} -D"
end
has been updated to this:
task :start, roles: :app, except: { no_release: true } do
sudo "cd #{current_path} && bundle exec unicorn " +
"-c #{unicorn_config} -E #{rails_env} -D"
end
Both of these depend on unicorn_config
being set in the config:
set :unicorn_config, "#{current_path}/config/unicorn.rb"
As I said, RVM was working fine and had been for a long time, and I thank its authors and contributors for all the hard work that went into it.
That being said, I’m glad I took the time to switch to rbenv, which is a lot leaner and easier to understand. These were the two main factors in my decision to switch, and for me, they were worth it.
I feel more confident now that I know how Ruby is being made available to the apps and scripts on my server.
Here is the Unicorn init.d
script I use for Rails apps. It has pieces taken from lots of different sources.
#!/bin/sh
### BEGIN INIT INFO
# Provides: unicorn
# Required-Start: $all
# Required-Stop: $all
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: starts the unicorn app server
# Description: starts unicorn using start-stop-daemon
### END INIT INFO
set -e
USAGE="Usage: $0 <start|stop|restart|upgrade|rotate|force-stop>"
# app settings
USER="admin"
APP_NAME="myapp"
APP_ROOT="/home/$USER/apps/$APP_NAME/current"
ENV="production"
# environment settings
PATH="/home/$USER/.rbenv/shims:/home/$USER/.rbenv/bin:$PATH"
CMD="cd $APP_ROOT; bundle exec unicorn -c config/unicorn.rb -E $ENV -D"
PID="$APP_ROOT/tmp/pids/unicorn.pid"
OLD_PID="$PID.oldbin"
# make sure the app exists
cd $APP_ROOT || exit 1
sig () {
test -s "$PID" && kill -$1 `cat $PID`
}
oldsig () {
test -s $OLD_PID && kill -$1 `cat $OLD_PID`
}
case $1 in
start)
sig 0 && echo >&2 "Already running" && exit 0
echo "Starting $APP_NAME"
su - $USER -c "$CMD"
;;
stop)
echo "Stopping $APP_NAME"
sig QUIT && exit 0
echo >&2 "Not running"
;;
force-stop)
echo "Force stopping $APP_NAME"
sig TERM && exit 0
echo >&2 "Not running"
;;
restart|reload|upgrade)
sig USR2 && echo "reloaded $APP_NAME" && exit 0
echo >&2 "Couldn't reload, starting '$CMD' instead"
$CMD
;;
rotate)
sig USR1 && echo rotated logs OK && exit 0
echo >&2 "Couldn't rotate logs" && exit 1
;;
*)
echo >&2 $USAGE
exit 1
;;
esac
I know it’s possible to accomplish this now with what RVM has built in, but when I wrote this script, this functionality either wasn’t available or I simply couldn’t find it, so I wrote my own. I’m including it here only for the sake of completeness.
#!/bin/bash
if [[ -s "/usr/local/rvm/scripts/rvm" ]] ; then
source "/usr/local/rvm/scripts/rvm"
if [ -f gemset ] ; then
rvm 1.9.2@`cat gemset`
elif [ -f config/gemset ] ; then
rvm 1.9.2@`cat config/gemset`
else
rvm 1.9.2@scripts
fi
$1
else
echo "ERROR: An RVM installation was not found."
fi
Apr 25 '17 at 08:49 am
Try running rbenv local 2.1.5
in your app’s root directory. This will create a a .ruby-version
file containing the string 2.1.5
. You can also add ruby '2.1.5'
to your Gemfile.
Apr 22 '17 at 09:00 pm
I’m really suspicious of the line in this script
ENV="production"
I’m pretty sure it should be
RAILS_ENV="production"
I used a similar script, and everything seemed fine, but unicorn had no idea it was production because the environment wasn’t set correctly.
Apr 25 '17 at 08:52 am
The ENV
variable is specific to this script. It’s used as the argument to the -E
/--env
flag (to set the RACK_ENV
) when starting Unicorn:
CMD="cd $APP_ROOT; bundle exec unicorn -c config/unicorn.rb -E $ENV -D"
Lightrail – a solid, secure platform for insurance websites.
Find out what makes Lightrail awesome »
© 2024 nicknotfound.com
Vignesh
Jun 3 '16 at 11:19 pm
Hi,
Thank you for the post.
I have followed the steps, but for my case, the ruby version(2.1.5) which I mentioned in the deploy.rb is not taking while doing cap deploy. It’s taking the rbenv global ruby version(1.8). I can’t change the rbenv global version, because other applications are using this global version. Can you suggest me any other solution is available for that.