May 21, 2006

Deploying Rails with Edge and Engines to Dreamhost using Capistrano

Filed under: deployment, rails, tutorial — Casper Fabricius @ 12:59 pm

It’s not that I’m the first to describe the process of deploying a Rails application, in fact; it’s rather well-documented, also when it comes specifically to Dreamhost. It’s just that I have a strange combination of circumstances that none of these articles cover, and also; I know almost nothing about Unix-based systems - I don’t even have a Mac. I am one the of many Microsoft-developers who are starting to see the light, and because of that, I need to start entirely from scratch when it comes to understanding the whole Linux/Apache/open-source universe.

(Take me straight to the solution, please)

My strange combination of circumstances
The Rails application I’ve had to learn to deploy the hard way has this configuration:

  • Dependent on Rails 1.1.2
  • Uses Login and User Engine.
  • Build for MySQL (so no problem there)
  • Must run with FastCGI, or it’s too slow
  • Developed with RadRails on Windows XP (yes, this makes a difference)
  • Resides in a Subversion repository

If you can live with Rails 1.0.0, if you don’t use Rails Engines, if you don’t need FastCGI and if you know about Linux, you are probably better off using shorter and simpler tutorials, because these where my specific problems, and what I am going to dvelve into here.

Linux basics
Although it is possible to deploy Rails to a Windows server (and presumably not much harder if you can use Apache instead of IIS), Dreamhost, and all other commercial hosts supporting Ruby on Rails, are using Linux servers. You can develop your entire Rails application completely unknowningly about Linux, but when it gets to deployment you have to learn the basics about this strange operating system:

  • You don’t use Remote Desktop, you use SSH. Remember DOS? When you connect with SSH, you get a text prompt simular to that, and you need to know some basic commands. I recommend using PuTTY for SSH.
  • Use pwd to print you current directory.
  • Use ls to print the contents of the current directory.
  • Use cd (e.g. cd /home/user/) to move into a directory.
  • Use vim (e.g. vim environment.rb) to quickly view and edit files directly on the server. Press your INSERT button to enable editing, and press ESCAPE, then write :wq to save and exit the file.

Now, in a perfect world (see below) you actually shouldn’t have to know this, because Capistrano would do all the dirty work for you, but let’s get real: It’s just not that easy.

In a perfect world
Here is a quick recap of what you would be all needed to do, if the world was perfect or you are extremely lucky and everything works as expected

  1. Now, I expect you have already created MySQL database on Dreamhost and entered the connection details into config/database.yml - otherwise; start out with that.
  2. Create a new fully hosted domain in the Dreamhost panel, and make sure it is configured like this:
    Dreamhost Rails setup
    • FastCGI Support enabled
    • The user has shell access of the type /bin/bash/ (configurable from the Manage Users page)
    • The web directory points to [yourdomain.com]/current/public (not just [yourdomain.com]/public, since we want to use Capistrano)
    • (The www option is up to you)
  3. SSH to [yourdomain.com] and remove the current directory (containing the public directory) Dreamhost has automatically created. (Capistrano needs to create this as a symbolic link later.):
    # On your Dreamhost server
    [nimitz]$ pwd
    /home/cape
    [nimitz]$ cd myrailsapp.com/
    [nimitz]$ ls
    current
    [nimitz]$ rm -r current/
    [nimitz]$ ls
    [nimitz]$
    • Start PuTTY, enter [yourdomain.com] as Host Name and click Open.
    • Login with the user you specified when creating the domain
    • Change to the directory of your Rails application (cd [yourdomain.com])
    • Remove the directories (rm -r current)
  4. Download Geoffrey Grosenbach’s Capistrano script, add it to the /config/ directory of your Rails application, and edit it to reflect your setup:
    # In config/deploy.rb
    set :user, 'cape'
    set :application, 'myrailsapp.com'
    set :repository, 'http://svn1.cvsdude.com/...'
    set :svn_username, "casper"
    set(:svn_password) { Capistrano::CLI.password_prompt }
    • :user must match the user you specified when creating the domain
    • :application must be the domain you created
    • :repository must be the url of your Subversion repository for the application, from where Capistrano will grab the code to deploy.
    • :svn_username only have to be present, if your subversion username is different from your Dreamhost shell username.
    • set(:svn_password) { Capistrano::CLI.password_prompt } tells Capistrano to prompt you for your subversion password. This might not be nescessary, if your Dreamhost user is already in a trust relationship with the Subversion server.
  5. Edit public/.htaccess and change the line RewriteRule ^(.*)$ dispatch.cgi [QSA,L] to RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]. This tells Apache which file it should execute when a request comes in.
  6. Edit public/dispatch.fcgi and change the first line from #!c:/ruby/bin/ruby (or something simularly Windowish) to #!/usr/bin/env ruby. This line is called a shebang location and tells Linux where to find the program that are able to execute this file, in our case; this is ruby.
  7. Edit config/enviroment.rb and incomment the line: ENV['RAILS_ENV'] ||= 'production'. Dreamhost says they set this enviroment variable automatically the nice way when using FastCGI - in my experience, they don’t. This means you have to incomment the line every time you release, and outcomment it again the develop in the development environment - in my final deploy.rb I have a much cleaner solution to this problem, so stay tuned.
  8. Open up a Command Window, move into the directory of your Rails Application and run the command rake remote:exec ACTION=setup. (You will need to type your password for the user you specified when creating the domain)
  9. Run rake deploy. If you are really, really lucky, this will actually do the job for you, and you can browse to your application. If you are not, go on …

Finding out what’s wrong
I’m going to assume that the steps above sort of worked for you, such that Capistrano was actually able to setup the server and copy the files to the correct directory, but it probably failed by the end of the rake deploy command, because it didn’t have the permissions to restart the web server. So now when you go to [yourdomain.com] in your browser, you don’t get a “Page not found”, you get a “Rails application failed to start properly” after a looong wait.

Since the “Rails application failed to start” message is not very information, I am going to start out by giving you a few tips about how to diagnose your failing Rails application. You should be able to distinguish between to scenarios:

  1. Your Rails application is not able to run, probably because it can’t connect to the database or can’t locate some files
  2. The application can run, but the FastCGI link doesn’t work, so pages cannot be served

It’s easy to test for the first scenario; just SSH to the server, cd to the directory of your app (cd /home/user/[yourdomain.com]/current/), and run the command ruby script/console production. This attempts to start your Rails app in production mode, and any error messages will be right there on the screen for you.

The second scenario is in fact also pretty easy to test, at least under the assumption that Apache is able to redirect to “dispatch.fgi” (but it wasn’t, you probably wouldn’t get the “Rails application failed to start properly” error). By running ruby public/dispatch.fgi you actually invoke the same file as the web server, which will often give you handy information.

Once you application is running, you can also get useful debugging information from the log files in the logs directory, but if your Rails app is actually writing to these, you probably have something that are pretty close to work.

Fixing deploy.rb
As I have hinted, I had to make a couple of changes to Geoff’s “shovel” deploy.rb in order to make it run smoothly:

  1. To be able to restart the FastCGI-processes in the :restart task, I had to add ruby to the line that restarts, because the reaper-script that restarts is not executable on Dreamhost. So my :restart task now looks like this:
    # In config/deploy.rb
    task :restart, :roles => :app do
      run "ruby #{current_path}/script/process/reaper --dispatcher=dispatch.fcgi"
    end
  2. To make Apache able to actually execute dispatch.fcgi our deploy.rb also needs to run a command makes dispatch.fcgi executable. On Unix-based systems, any file can be marked as executable, and the OS will then read the shebang location when the file is invoked. By inserting the task below, dispatch.fcgi will be made executable each time you make a deployment. By naming the task after_symlink, it will be executed after the symbolic link has been moved to point to your new files - first by then, the current_path variable is available.
    # In config/deploy.rb
    task :after_symlink, :roles => [:web, :app] do
      # Make dispatcher executable
      run "chmod a+x #{current_path}/public/dispatch.fcgi"
    end

Rails in the /vendor directory
It’s supposed to be so easy to use a different version of Rails than the one installed on your host. Just run rake rails:freeze:gems - or even better: Set /vendor/rails to point to http://dev.rubyonrails.org/svn/rails/tags/rel_X-X-X/ (depending on your prefered version) using the svn:externals feature of Subversion. However, strange things can happen to your application, once all the core files of Rails are suddenly supposed to be read from the /vendor/rails directory. I had to make three crucial changes to avoid various “required file not found” or “NoMethodError” messages after I “froze” Rails:

  1. Move the inclusion of files in config/environenment.rb down to the bottom of this file. For instance; I have require 'taggable' in my environment.rb, and before I “froze” Rails it I just had this line at the top without any problems, but after, it suddenly couldn’t find the taggable stuff, and it only worked when I moved that line down after the whole Rails::Initializer.run do |config| chunk.
  2. Force public/dispatch.fcgi (the file that runs the show) to use the “frozen” version of Rails, by replacing the line require 'fcgi_handler' in dispatch.fcgi with this line:
    require File.dirname(__FILE__) + "/../vendor/rails/railties/lib/fcgi_handler"
  3. Unfortunately, we also need to edit a file inside the Rails framework itself, putting in the absolute location of a required file instead of just the name of the file, as described in this post. However, if you use svn:externals like I do, you can’t just alter a file in vendor/rails, since you don’t have any commit rights. Furthermore, the location of the line we need to add is specific to our setup on the host, so it would be baaad to hardcode it. That’s why I came up with a solution letting the deployment script replace this line on the server using the current_path variable available. You could do this with ruby, but I asked one of my hardcore Linux geek friends to write a one-liner that I could just run, and he chose to let Perl make the replace directly in the file for us. This also happens in the after_symlink task:
    # In config/deploy.rb
    task :after_symlink, :roles => [:web, :app] do
      # Ensure Rails in the vendor dir is used by replacing a line in fcgi_handler.rb
      run "perl -i -pe \"s/require 'dispatcher'/require '#{current_path.gsub(/\//, '\\/')}\\/vendor\\/rails\\/railties\\/lib\\/dispatcher'/\" #{current_path}/vendor/rails/railties/lib/fcgi_handler.rb"
      ...
    end

Rails Engines
Rails Engines is a brilliant way of creating “super-plugins” that acts like small sub-applications inside your application, but where everything can be extended or overriden - even views! If you don’t use engines, this small section is irrelevant for you, but I’d recommend you taking a look a them - I love them.

There is really only one thing you need to be aware of when you deploy a Rails application to Dreamhost that uses engines and has Rails in the vendor directory, and that is to put this code at the very top of your environment.rb:

# In config/environment.rb
module Engines
  CONFIG = {:edge => true}
end

One other thing, not really related to deployment: As I am writing this, the LoginEngine has a problem loading two files in its init_engine.rb, so if you get errors in this file, try replacing the two original require statements with this:

# In vendor/plugins/login_engine/init_engine.rb
require 'login_engine/authenticated_user'
require 'login_engine/authenticated_system'


Wrapping it up: The complete solution
Today, I am able to run rake deploy and it actually works. My deployment script for Capistrano, based on Geoff’s shovel, can be downloaded here. Notice that this script, besides the tasks described above, also automatically replaces #ENV['RAILS_ENV'] ||= 'production' with ENV['RAILS_ENV'] ||= 'production' (incomments it) in config/environment.rb to force production environment on the Dreamhost server.

Here is a complete list of files I’ve uploaded for your reference:

I had to rename some of the files in order to let them be downloadable, but the link listed above has the correct name and the directory the file belongs to.

Please note: You can’t just dump these four files into your own Rails application and expect things to work. You have to carefully extract the pieces from each file you think you need in order to get your application up and running.

Good luck!


Recommended ressources