Not all Ruby on Rails applications live on the internet within arms length of devops. Some appliations like our own Trisul have to be installed within the enterprise by folks who have no Ruby or Rails skills. A common way to address this use case is to just package the software as a VM image in a format such as OVF – but sometimes the customer just wants to install your package.
This post describes how you can distribute your app as a single tarball containing the correct version of Ruby with all gems and your app bundled. The downside of this approach are :
- Platform dependent – you need to distribute tarballs for major OS and 32/64 bit architectures. We found that between CentOS and Ubuntu we can hit a majority of users.
- Install location is fixed – your app will install in a common place such as /usr/local/*/{SoftwareName} or /opt/{SoftwareName}/*. The data can be anywhere your app decides to store it.
Without much ado, lets jump right in with an example. We will use a Rails 3 app called Snorby who feel a real world need for this packaging.
Toolbox
The magic ingredient is the most excellent project rbenv written by Sam Stephenson of prototype.js fame. This project allows you to run multiple versions of Ruby by Shimming all your executables. You also need Bundler which includes a command called bundle exec which takes care of running your app within context of a set of Gems
Follow these steps to make the tarball.
- replace Snorby with your application name
- replace /usr/local/share with your preferred install area such as /opt
Step 1 – Dev machine
Set aside a development machine which will be used to make the tarball. You also need a pristine machine which you will use to test the tarball.
Step 2 – Install rbenv & prepare the staging area
We will install Snorby under /usr/local/share/snorby – so we need to install rbenv there
1 2 3 |
mkdir /usr/local/share/snorby cd /usr/local/share/snorby git clone git://github.com/sstephenson/rbenv.git .rbenv |
Now add rbenv shell paramaters.Add these three lines to your ~/.bashrc file
1 2 3 |
export RBENV_ROOT=/usr/local/share/snorby/.rbenv export PATH=$RBENV_ROOT/bin:$PATH eval "$(rbenv init -)" |
Dont worry, the user of the tarball does not have to do this. Just you the package builder.
Close your terminal and open a new one . You are all set to install Ruby
Step 3 : Install Ruby into the staging area
We turn to ruby-build which makes it easy to install various versions of Ruby into specific locations. You could probably do this by hand by compiling from source and using the –prefix switch during compilation.
Install ruby-build
1 2 3 |
git clone git://github.com/sstephenson/ruby-build.git cd ruby-build sudo ./install.sh |
Install your version of Ruby into the staging area
1 |
ruby-build 1.9.2-p290 /usr/local/share/snorby/.rbenv/versions/1.9.2-p290 |
Switch and check ruby version
1 2 3 |
cd /usr/local/share/snorby rbenv local 1.9.2-p290 ruby --version |
The ‘rbenv local’ command sets the ruby version for the base directory (/usr/local/share/snorby) to 1.9.2-p290. If you cd to another directory – rbenv switches the version back to the system ruby (or none if there is no Ruby installed)
Step 4 : Unzip your rails app
Unzip your snorby rails app into /usr/local/share/snorby. Note this should be the Rails.root directory containing your Gemfile.
Step 5 : Install Gems
The first step is to install bundler, which takes care of the rest
1 2 3 4 |
cd /usr/local/share/snorby rbenv local 1.9.2-p290 gem install bundler rbenv rehash |
The rbenv rehash is required to shim the new bundler executable. Type bundle –version to check if bunder was installed correctly.
Install all your app gems
1 |
bundle install |
In case some gems fail to to library dependencies – install them. For Snorby I had to install 3 dependencies. Once again – dont worry because the end customer doesnt need to do any of this. Thats the whole point of the tarball.
1 2 |
sudo apt-get install libxml2-dev libxslt-dev libmysqlclient-dev libmagick9-dev |
After correcting errors – repeat the following until all gems are installed.
1 2 |
bundle install rbenv rehash |
Again dont forget the “rehash”.
You are almost there, check if your apps works in this staging area. Use bundle exec on your rake tasks or start your app.
1 2 |
bundle exec rake -T bundle exec thin start |
Step 6 : Startup script
You cant expect the user to be logged on with a custom bashrc in order to start your app. So you need to prepare a tiny startup script which includes the required shell variables at the top.
The trick is to use bundle exec command instead of just command or ruby command.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
# root area SNORBY_HOME=/usr/local/share/snorby # from rbenv export RBENV_ROOT=/usr/local/share/snorby/.rbenv export PATH=$RBENV_ROOT/bin:$PATH eval "$(rbenv init -)" # use a file to run rake setup tasks on first run if ! test -e $SNORBYHOME/db/first; then echo "Running first time install steps for snorby.." cd $SNORBYHOME echo "--- setup ----------------" bundle exec rake snorby:setup touch $SNORBYHOME/db/first echo "--- jobs ----------------" bundle exec script/delayed_job start bundle exec rails runner 'Snorby::Jobs:: SensorCacheJob.new(false) .perform; Snorby::Jobs::DailyCacheJob.new(false).perform' echo "Done initializing snorby" fi # start the webserver bundle exec thin start -e production -d |
Step 7 : Tar it
Just tar the whole directory /usr/local/share/snorby
1 2 |
cd /usr/local/share/ tar cfz snorby.tar.gz snorby |
Thats it !
Step 8 : Test it
On a pristine machine of the same architecture test your tarball.
1 |
tar xfz snorby.tar.gz -C /usr/local/share/ |
Run your script from Step 6
1 |
/usr/local/share/snorby/thind start |
Hope this helps folks looking to distribute Rails based apps in the enterprise. Please leave a comment if you think I am missing something. I feel an improvement can be made by allowing the user to untar to any location – I was never able to get it to work because the library paths are all over the core ruby install.