Deploying Rails Applications
on Mac OS X Leopard

In Developing Rails Applications on Mac OS X Leopard, we created a web application using the latest Rails features with Xcode 3.0. In Customizing Rails Applications on Mac OS X Leopard, we learned how to customize views, create web forms, and add AJAX and iPhone support. In this third article, we'll finish up by deploying the Rails application on Mac OS X Leopard Server.

Traditionally deployment has been painful because it involves getting all the required software installed, configuring various moving parts, remembering to copy the right files at the right time, and so on. Leopard Server changes all that. In addition to Ruby and Rails, Leopard Server comes pre-installed with everything we need to deploy and run a production Rails application: Apache 2.2, mod_proxy_balancer, MySQL, Mongrel, Capistrano, and a few other unique goodies. Indeed, Leopard Server raises the bar when it comes to ease of Rails deployment.

In a short amount of time we've built a functional expense-tracking application. Developing on Leopard gave us a big productivity boost with its built-in support for Ruby and Rails. Deploying our Rails application should be no different. So now we're going to transition over to Leopard Server for another boost.

Updating Ruby Gems

The first thing we need to do is update the necessary Ruby gems to their latest versions. Log in to your production server running Leopard Server and type these commands:

sudo gem update --system
sudo gem install rails
sudo gem update rake
sudo gem update capistrano
sudo gem update mongrel

In addition to updating the RubyGems system, Rails, and Rake as we did on our development machine, these commands also update two additional gems we'll need for deployment. Capistrano is an automated deployment utility and Mongrel is a popular web server for running Rails applications. Running all these commands may take a while. When you've finished, double-check that you're using the latest version of Rails (2.0.2 for this article) by typing

rails -v

Setting Up the Production Database

We developed this application using a SQLite3 database. In production, we'll switch over to a MySQL database. MySQL 5.0.45 is pre-installed on Leopard Server, and configuring our application to use MySQL in production couldn't be easier.

By default, all Rails applications have three runtime environments: development, test, and production. When our application runs in the production environment, it changes its personality: it runs faster, logging is less verbose, users see friendlier error pages, and so on. Equally important, in production the application uses the database that's configured in the production section of the config/database.yml file.

On your development machine, update the production section of the config/database.yml file inside your Rails application directory to use MySQL as follows:

production:
  adapter: mysql
  database: expenses_production
  username: root
  password: your-mysql-root-password_here
  socket: /var/mysql/mysql.sock

Then, back on the server, open the Server Admin app and start the MySQL Service, if it's not already running. Next, open a Terminal and create the expenses_production database by typing

mysqladmin -u root -p create expenses_production

Now, before we run our application in production we'll need to make sure the schema in the expenses_production MySQL database matches the schema in our SQLite3 development database. Here's where database migrations really shine.

Remember, our migration files in the db/migrate directory consist of database-agnostic Ruby code that represents our schema. That means we can run the migrations against any database supported by Rails. We just need to make sure to apply all the migrations to our production database when we deploy the application. We'll take care of that a little later.

Putting the Code Under Version Control

Before we go any further, let's get our application code safely tucked away in a Subversion version control repository. Mac OS X 10.5 Leopard ships with Subversion 1.4.4 pre-installed, so it's easy to start getting the benefits of version control early.

First, we need to create a Subversion repository. We'll put it in the /Users/Shared/svnrepo directory on our server, but it can live anywhere. We just need it to be accessible from our production server. To create the repository, log in to your server and type

svnadmin create /Users/Shared/svnrepo

Next, we need to import our Rails application into the repository. Back on your development machine, make sure you're in the expenses directory containing the Rails application. Then, substituting in your server name, type

svn import -m 'Initial import' . \
  svn+ssh://your-server-name/Users/Shared/svnrepo/expenses

At this point, we have a copy of our Rails application in the Subversion repository on the server. That means we can check out a copy of the application onto any machine that has secure shell (SSH) access to our server. To do that on your development machine, for example, you'd type

svn co svn+ssh://your-server-name/Users/Shared/svnrepo/expenses

We'll use the version of our application in the Subversion repository as the "gold master" that gets deployed to our production server.

Creating a Deployment Recipe

Deploying our application to the production server is something we plan to do often. As new features are developed and bugs are fixed, we'll want to re-deploy the application without a lot of fuss. We don't want to have to log in and out of servers, copy files around, and run a checklist of tasks manually every time we deploy the application. Instead, we want to automate the deployment process so that it's consistent and repeatable.

Leopard includes the Capistrano automated deployment utility which makes deploying Rails applications a breeze. All the deployment ingredients and steps are neatly spelled out in a deployment recipe file. Every time you want to deploy a new version of your Rails application, you simply type one command.

Back on your development machine, change directory to the expenses directory containing the Rails application and type

capify .

This command creates a template Capistrano recipe in the config/deploy.rb file. Clear out the file and add the following sections incrementally as we go.

First, Capistrano needs the deployment ingredients. Add the following variables and settings in the config/deploy.rb recipe file:

set :application,  "expenses"

set :repository,   "svn+ssh://your-server-name/Users/Shared/svnrepo/#{application}"
set :deploy_to,    "/Library/WebServer/#{application}"
set :deploy_via,   :export

set :scm_username, "your-subversion-username"
set :scm_password, "your-subversion-password"

ssh_options[:forward_agent] = true

The application variable is just an arbitrary name for our application. The repository variable is where the master copy of our application source code lives. In this case, Capistrano will use the svn+ssh protocol to access the Subversion repository we set up previously. (Capistrano supports other version control systems, as well.) The deploy_to variable is the name of the directory on the production server where the application will get deployed. You'll need to set the scm_username and scm_password variables for your environment. The last line may be important depending on how your SSH keys are set up. Basically, it lets the SSH connection to the Subversion repository use your SSH agent, if one is running, so you don't have to enter passwords repeatedly.

With this part of the recipe complete, Capistrano knows how to check out the Rails application from our Subversion repository and deploy it into the /Library/WebServer directory on the production server. The one missing ingredient is the name (or IP address) of the production server.

Add the following to your recipe file, substituting your server name or IP address:

role :app, "your-server-name.com"
role :web, "your-server-name.com"
role :db,  "your-server-name.com", :primary => true

A role is simply a named group of servers that Capistrano uses to specialize the behavior of certain deployment tasks. In this case, we're deploying to a single server and the role distinction doesn't matter. However, roles become important when you start to scale up with multiple servers, as we'll see later.

That takes care of the deployment ingredients. Next, Capistrano needs to know the deployment steps, or tasks. It has a number of default tasks including start, stop, and restart. We'll define our own versions of those tasks to start, stop, and restart our Rails application.

We'll be running our Rails application using Mongrel, which is a web server pre-installed on Leopard for running Rails applications. Mongrel has a mongrel_rails command for starting and stopping Mongrel processes. To streamline deployment, Leopard Server includes an enhanced version of the mongrel_rails command called mongrel_rails_persist. It creates a launchd plist file to run a Mongrel process persistently across reboots. It also registers the Mongrel process with Bonjour so that clients looking for httpd services can find the Mongrel process.

Add the following Mongrel-specific variables to the recipe file:

set :mongrel_cmd, "/usr/bin/mongrel_rails_persist"
set :mongrel_ports, 3000..3003

set :user,  "administrator"
set :group, "admin"

These aren't special variables; we've just defined them here to keep the rest of the recipe tidy. The first two variables define the full path to the mongrel_rails_persist command and a range of ports (3000-3003) for each Mongrel process. In this case, we'll be starting four Mongrel processes. We'll use the user and group variables simply to set the user and group ownership for each process.

Next, add the following Capistrano tasks to your recipe file:

namespace :deploy do

  desc "Start Mongrels processes and add them to launchd."
  task :start, :roles => :app do
    mongrel_ports.each do |port|
      sudo "#{mongrel_cmd} start -p #{port} -e production \
            --user #{user} --group #{group} -c #{current_path}"
    end
  end

  desc "Stop Mongrels processes and remove them from launchd."
  task :stop, :roles => :app do
    mongrel_ports.each do |port|
      sudo "#{mongrel_cmd} stop -p #{port}"
    end
  end

  desc "Restart Mongrel processes"
  task :restart, :roles => :app do
    stop
    start
  end

end

The start and stop tasks loop through each port number (3000-3003). Each time through the loop, the sudo method is used to run the mongrel_rails_persist command, which starts or stops a Mongrel process on the appropriate port. Notice that the start task starts our Rails application in the production runtime environment using the -e option.

It's important to note that the sudo method runs the mongrel_rails_persist command on the production server(s) listed in the app role via the sudo command. Capistrano uses the secure shell (SSH) to execute these command remotely. You can pass an :as option to the sudo method to specify who the command is executed as. Alternatively, you can use the run method to run the command on the production server without sudo privileges.

The restart task simply runs the start and stop tasks in succession.

Deploying for the First Time

Capistrano assumes we'll want our application deployed to a consistent directory structure on all the production machines. It's the directory we specified in the deploy_to variable, which in this case is /Library/WebServer/expenses. We haven't created that directory yet. Rather than logging into our server and creating the directory manually, from this point forward we'll use Capistrano to run tasks remotely for us.

On your development machine, type

cap deploy:setup

In the output you'll see Capistrano log in to your production server using SSH and create the following directory structure:

/Library/WebServer/expenses/
/Library/WebServer/expenses/releases
/Library/WebServer/expenses/shared
/Library/WebServer/expenses/shared/log
/Library/WebServer/expenses/shared/system
/Library/WebServer/expenses/shared/pids

Now we're ready to deploy our Rails application for the first time. This is somewhat of a special case. Typically our application will already be running on the production server, and we'll just redeploy it. The first time we deploy, however, the application isn't running. Moreover, we haven't applied the database migrations to our production database.

For a first-time deployment, run a special "cold" deploy task by typing

cap deploy:cold

In the output you'll see that this task logs in to your production server via SSH, checks out the application code from Subversion into a new directory under the /Library/WebServer/expenses/releases directory, and then creates a /Library/WebServer/expenses/current symlink that points to the new release. This current symlink is used as the root directory of the currently-active release.

The task then runs all the migrations so that our production database schema matches our development database schema. Finally it runs the start task we wrote earlier to fire up all four Mongrel processes for the first time.

At this point your application is running on the production server. Assuming you're logged in to your production server, point Safari at http://127.0.0.1:3000/events and you should see the expenses application.

Configuring the Web Server

We're able to access our application by talking directly to a Mongrel process via a port. Now we need to configure the Web Service to transparently proxy all incoming requests to our pack of Mongrel processes.

Follow these steps to configure the Web Service on Leopard Server:

  1. Open the Web Service. On the production server, open the Server Admin app and connect to the server. Then click the triangle to the left of the server. From the list of services, select Web.

  2. Configure the Web Service. Click Sites and select the website in the list. Then click Proxy below the websites list. Select the Enable Reverse Proxy checkbox and verify that the Proxy Path field is set to "/". This will proxy all URLs within the website to the balancer members.

  3. Add Balancer Members. Each balancer member corresponds to a Mongrel process, running on either the local host or other hosts. Click the Add (+) button below the Balancer Members list. In the Server URL pop-up menu, you should see four Mongrel processes listed with unique URLs as shown in Figure 14.

    Server Admin 1

    Figure 14: Leopard Server Admin: Adding Balancer Members

    These are the Mongrel processes that were started when we cold deployed the application. Server Admin was able to find the processes because the mongrel_rails_persist command registered them with Bonjour.

    Select a balancer member and set its Load Factor to 25 to distribute the load equally among balancer members. Leave the Route field blank unless you have a specific reason to enter a value.

    Repeat this step until all four balancer members have been added. Figure 15 shows the final configuration.

    Server Admin 2

    Figure 15: Leopard Server Admin: Configuration for a dedicated website

  4. Save the Configuration. Click Save and then click Start Web Service, if it's not already running.

  5. Confirm Access. Point Safari at http://127.0.0.1/events to confirm that the URL is proxied to the Rails application.

Using this configuration, our website (virtual host) is dedicated to the Rails application. Alternatively, we can arrange things so that the website is shared with the Rails application. Say, for example, we want the Rails application to be mounted under the /expenses URI.

To do that, simply change the mongrel_rails_persist command in the start task of the Capistrano recipe to include a --prefix /expenses option.

sudo "#{mongrel_cmd} start -p #{port} -e production \
  --prefix /expenses --user #{user} --group #{group} -c #{current_path}"

Then, in the Proxy Path field of the Web Service configuration, enter the prefix with both a leading and trailing slash. In our example this would be /expenses/. When you add Balance Members, the Server URLs shown in the pop-up menu will include /expenses as shown in Figure 16.

Server Admin 3

Figure 16: Leopard Server Admin: Configuration for a shared website

Then to access the shared application, point Safari at http://127.0.0.1/expenses/events.

Deploying New Releases

After the first deployment, things get even easier. When you have a new release you want to deploy, first make sure all your code is checked in to the Subversion repository. Then, on your development machine, redeploy by typing

cap deploy

That command checks the latest version of your Rails application out into a timestamped subdirectory of the releases directory, flips the current symlink to the new release, and restarts all the Mongrel processes so that they pick up the new code.

If there's a problem with the new release, you can easily roll back to the previous release by typing

cap deploy:rollback

Rolling back is painless (and fast!) because it just updates the current symlink to point to the previous release and restarts all the Mongrel processes.

Scaling Up

In this example we deployed to a single Leopard Server. As your application becomes more popular, eventually you may want to start building a cluster of Leopard Servers, each with their own speciality. Capistrano makes it very easy to add new servers to your deployment process.

First, simply tell Capistrano about each server, and what role each plays. Here's an example:

role :app, "your-app1-server.com", "your-app2-server.com", "your-app3-server.com"
role :web, "your-web1-server.com", "your-web2-server.com"
role :db,  "your-db-server.com", :primary => true

Next, set up each new server by typing

cap deploy:setup

Then deploy code as usual by typing

cap deploy

When you run a Capistrano task such as deploy or deploy:rollback, it's run in parallel (and atomically) on all machines that are assigned to the app role in the Capistrano recipe file. Think about that: As the number of machines and processes in your deployment environment varies, your deployment procedure remains constant and consistent. It's just one command.

Conclusion

Ruby on Rails is a highly-productive web application framework. It scales from the simplest expense tracking application to full-featured applications with respectable numbers of users. Leopard is there to help every step of the way. You can get a useful Rails application up and running quickly on your PowerBook, then seamlessly deploy it to a production-ready Xserve (or cluster of Xserves) running Leopard Server.

For More Information

This article has introduced you to Ruby on Rails on Leopard. The following references will help you dig deeper:


Updated: 2008-09-29