Learning Deployment - The Rails Way

And so I was up to Chapter 16: Task K - Deployment and Production of Agile Web Development with Rails 2 weeks ago. This is the last chapter of the hands on tutorial.

I was very excited arriving to this chapter as I was curious to see how Rails handles deployment. I couldn’t wait to get my hands dirty with setting up and installing parts that will hopefully form a smooth and automated deployment process.

Back on tutorial itself, the application was run on the standard Rails setup for development ie: WEBrick (Ruby’s own application server) and SQLlite for the database. Now, in this chapter, to make it more a real world like, we would deploy the application on Apache and MySQL.

It wasn’t exactly smooth sailing process, in this post, I will try to describe the problems that I encountered and how I solved (or ignored)┬áthem as well as questions that I have. Plus a short description on my failed attempt to deploy to Heroku (boo!).

If you follow the example in the book - the aim of this exercise is to actually “mimic” a production deployment - only we are not deploying our app remotely but rather locally.

First off - I did an install of Phusion Passenger - I am not sure what it is (a server or a connector?) - but it allows you to run Ruby on Rails on an Apache server.

Then we need to change Apache configuration. First of we need to “connect” Apache to Passenger - this is done by adding the following lines on my apache2.conf file.

LoadModule passenger_module /home/felixt/.rvm/gems/ruby-1.9.2-p180/gems/passenger-3.0.7/ext/apache2/mod_passenger.so
PassengerRoot /home/felixt/.rvm/gems/ruby-1.9.2-p180/gems/passenger-3.0.7
PassengerRuby /home/felixt/.rvm/wrappers/ruby-1.9.2-p180/ruby

Then we need to add our “production” site on Apache virtual host - which happens to be on /default/000 on my installation

<VirtualHost *:80>
ServerName movie-depot.production.passionate.int

RailsEnv production

DocumentRoot /home/felixt/prj/movie-depot-production/public
<Directory /home/felixt/prj/movie-depot-production/public>
AllowOverride all
Options -MultiViews

Noticed the RailsEnv production line there? That’s neat - that’s how Passenger knows which application it’s serving, other than that it’s a vanilla Apache virtual host entry.

Restart Apache - browse to the site and I got a database error. Sure enough I haven’t created my production database.

Should you put your database.yaml on Github?

And off I went, fixing the database.yaml, creating database, creating user etc2. Then it dawns to me that this database.yaml is plain text - if I push it to Github, people would see the db username and password - definitely a security concern here.. But anyway that’s a thought for another day - I am pretty sure there are workaround for this - the manual way would be to put dummy value when pushing it to Github.

Reload Passenger

To reload Passenger you need to “touch” the file restart.txt on tmp directory. This is a minor inconvenience when you are developing, this is different to WEBrick which reload itself everytime changes are made (yes - there must be a work around - but I am not looking into that now).

Enter Capistrano

Next step is getting Capistrano to look after the deployment (locally in this case).

I setup the deployment script as described in the book. My deployment script is a little bit different - rather than checking out from local git server, I actually made it so that it checked out from the application Github repository.

And when I ran the deploy setup, I got the following error:

* executing `deploy:setup'
* executing "sudo -p 'sudo password:
servers: ["felixt", "movie-depot-prod.passionate.int"]
connection failed for: felixt (SocketError: getaddrinfo: Name or service not known)

Umh ok, what does SocketError: getaddrinfo mean?

Install SSH Server!

Yep, that error above just means that you don't have SSH installed. And why do we need SSH you might ask? Capistrano uses SSH for its transfer protocol, so you need SSH server installed on the server that you want to deploy to (in this case your local computer). Since I am running Ubuntu 10.04 - here is the command to get SSH server installed... Hm on the second thought, I am too lazy to type - it's been taking few days now to get this post up.. So just go this blog post:Basic SSH Setup On Ubuntu 10.04 Lucid Lynx Using OpenSSH Server - I pretty much followed the instruction there and it worked for me.

Target Directory Owner

Ran the cap deploy:check once more - this time I've got a different error message. The error message is not cryptic this time - it told me I don't have permission to write to the target deployment directory (it was created by root - but I am running this deployment under my username). That's easy to fix:
sudo chown felixt:felixt /home/felixt/movie-depot-prod.passionate.int/*

RVM and Capistrano Gotcha

You might get ruby not found exception when running capistrano for the first time - this is easy to fix. As described in this article on Integrating RVM and Capistrano, you just need to add the following section to your deploy.rb
set :default_environment, {
'PATH' => "/home/felixt/.rvm/gems/ruby-1.9.2-p180/bin:$PATH",
'RUBY_VERSION' => 'ruby 1.9.2',
'GEM_HOME'     => '/home/felixt/.rvm/gems/ruby-1.9.2-p180',
'GEM_PATH'     => '/home/felixt/.rvm/gems/ruby-1.9.2-p180',
'BUNDLE_PATH'  => '/home/felixt/.rvm/gems/ruby-1.9.2-p180'  # If you are using bundler.

My Final deploy.rb

As mentioned earlier, I had to make some tweaks to the deploy.rb supplied from the book in order to make it work for my own setup, here is what I came up with in the end:
# Configurable values - change this
set :user, 'felixt'
set :domain, 'movie-depot-prod.passionate.int'

# File Paths
set :repository,  "git@github.com:cemeng/railsMovieDepot.git"
set :deploy_to, "/home/#{user}/#{domain}"

set :default_environment, {
  'PATH' => "/home/felixt/.rvm/gems/ruby-1.9.2-p180/bin:$PATH",
  'RUBY_VERSION' => 'ruby 1.9.2',
  'GEM_HOME'     => '/home/felixt/.rvm/gems/ruby-1.9.2-p180',
  'GEM_PATH'     => '/home/felixt/.rvm/gems/ruby-1.9.2-p180',
  'BUNDLE_PATH'  => '/home/felixt/.rvm/gems/ruby-1.9.2-p180'  # If you are using bundler.

default_run_options[:pty] = true

# Distribute the app across servers (the instruction below put them all on the same server, defined
# above as 'domain', adjust as necessary)
role :web, domain
role :app, domain
role :db, domain, :primary => true

# Missc
set :deploy_via, :remote_cache
set :scm, 'git'
set :branch, 'master'
set :scm_verbose, 'true'
set :use_sudo, 'false'

namespace :deploy do
	desc "Initiate restart on Passenger"
	task :restart do
		run "touch #{current_path}/tmp/restart.txt"

	desc "Reload database with seed data"
	task :seed do
		run "cd #{current_path}; rake db:seed RAILS_ENV=production"

after "deploy:update_code", :bundle_install
desc "Install the necessary prerequisites"
task :bundle_install, :roles => :app do
	run "cd #{release_path} && bundle install"


I haven't been able to deploy this app to Heroku as yet - I got the following error when I tried to do so:
-----> Gemfile detected, running Bundler version 1.0.7
       Unresolved dependencies detected; Installing...
       Using --without development:test
       Some gems seem to be missing from your vendor/cache directory.
       Could not find activesupport-3.0.5.rc1 in any of the sources
       FAILED: http://devcenter.heroku.com/articles/bundler
 !     Heroku push rejected, failed to install gems via Bundler
I will have to look into it later - or ask the Heroku support (apparently they have a very good customer support there..). The next step for me is to get Rails installed on my VivioTech CentOS VPS.

Concluding Thoughts

Again it feels nice how gems makes installing dependencies super easy. Integration between Ruby and Apache with Passenger looks very easy as well.

Deployment with Capistrano - looks to be the trickiest and somewhat limited (only supporting SSH). The deploy.rb (and recipes?) does seem to be less verbose compared to Ant script for example. I don’t know.. this is just an initial impression, I have always used Ant for ColdFusion deployment and I don’t see Capistrano offers anything that Ant doesn’t have (yet).