Deploying a Ruby on Rails Application on RHEL4/Plesk 8.2 with Mongrel, Apache and Pound
This is a collection of data and procedures that I’ve tested and researched over the past month to get a fully functional, load-balanced Mongrel cluster configuration running behind Apache and Pound, without breaking Plesk.
Disclaimers
This guide isn’t meant to be a comprehensive solution or to account for every possible situation. This is a short-sweet-to-the-point “how to” for Rails deployments on this specific server configuration.
As always, be smart and use your best judgement, not mine, when doing this stuff. Nobody knows your server like you do. Always do a full backup of your system before trying something new, like this - especially of your configuration files.
Code Notation
Due to layout restrictions on this site, I’ve taken code that was too long to fit on one line and wrapped it with a hanging indent. For example, if you were to see:
<%= code goes here blah blah blah path=/var/www/vhosts/yourdomain
.com/foo/bar =>
You should know that the full path is actually, in this example, /var/www/vhosts/yourdomain.com/foo/bar. Just merge what’s on the indented line with what’s immediately above it.
How This Guide Came About
I wrote my first Rails application toward the end of 2007 and deployed it on January 22, 2008. As it was a small, seemingly simple application, I thought that deployment would be a breeze. God I was wrong.
As just about every rails developer knows, deploying rails applications isn’t quite as easy as other programming languages. It’s certainly not like PHP with simple “upload and run” sweetness. Complicated though it is, you can deploy RoR applications, and this guide will show you how to do it on this server architecture.
Use a blank project first!
I can’t stress this enough. Create an empty rails app first, and set it up to run on a test or dummy domain on your server. If you don’t have a domain name you use for just testing and stupid/dummy/silly projects, just add a new domain to one of your production domains (i.e. it’ll *be* a subdomain, but you add it as a regular domain with physical hosting in Plesk). For example, testapp.mydomain.com.
You will make mistakes on your first deployment. Things will break. You will be Googling for information. Do this with a simple, no-frills application first to get the wrinkles ironed out. This is absolutely crucial.
You won’t have to do ALL of this EVERY time.
This guide is meant for a completely green/fresh install of your first Rails application on a production server. You won’t have to install PCRE and Pound every time, for example - just edit some configuration files. I’ll try to note in various places where you can skip steps the next time you’re deploying another app to the same server.
Software and Versions
First of all, I’d like to list version numbers of all software being used. This guide will of course become outdated, and some errors you run into could be due to changes in the versions of software.
- Ruby v. 1.8.6
- Apache v. 2.0.54
- Pound 2.3.2
- Capistrano 2.1.0
- Rails v. 2.0.2
- Red Hat Enterprise Linux 4, kernel 2.6.9-2.0.3.EL
- Plesk 8.2
- MySQL 4.2.20 (using readline 4.3)
Assumptions
I’m making some assumptions about you as a system admin and/or rails developer, and about your environment:
- You’re comfortable using the console, SSH, and Linux without a GUI
- You’ve finished coding and fully testing a rails application, and it’s ready for prime time
- You have Ruby, Rails, MySQL and Capistrano installed on your local machine and on your server (except Capistrano, you don’t need that on the production server)
- You have root on the system you’re deploying -to- and aren’t afraid to use it.
- Plesk vhosts are installed in /var/www/vhosts/
- You’re on Red Hat Enterprise Linux (probably version 4) as it says in the title
Understanding The Big Picture
Here’s how things work. First you develop your application and test it, get it ready to go. Then you have several tools at your disposal to help you deploy: Capistrano is the guy calling the shots here. Cap (for short) is a program that will shell into your server, checks out your code, and does some directory structure/linking magic to make it all work. Then it fires up your “internal” server (i.e. the one that runs on specified ports, not port 80) which makes the rails app actually run.
After that, you use a load balancer - Pound, in our case - to shuttle traffic and requests to one of several different instances of the Rails application. Basically, you’ll be running two or three (sometimes four or maybe as many as five) “copies” of your Rails application that all read and write to the same database. The end-user has no clue what’s going on behind the scenes (and of course doesn’t need to), but Pound switches traffic between all of the different “copies” (i.e. instances) of your application so as not to overwhelm any one process/instance of your application when under heavy load, thus increasing speed to the end user.
Finally, we use Apache’s ProxyPass feature to tie in seamless integration to Pound. Basically, this makes Apache forward all requests for a particular domain to Pound, which distributes the load evenly between all the different running applications.

Installing and Configuring Subversion
Our first stop will be installing and configuring Subversion. This is a version-control system that most “hard core” rails developers are using nowadays, and for some good reasons. It’s actually very stable and works quite well. It’s perfect for geographically distributed workforces, like the Rails core team(s), so companies with employees in different locations will find it a godsend.
First, go get the RPMs for Subversion from their site:
Subversion RPMs
As it says there, you need to download the client before the server. The client, at the time of this writing, is the second one on the list. Copy those URLs and paste it into wget inside your root directory for example, and install both the client, and the server:
rpm -Uvh <filename>
[Repeat for each file - client/server]
Next, you need to run the following script:
[root@yourbox ~]# Configure-CollabNet-Subversion
Follow the directions listed to configure it. I didn’t bother with the access via HTTP option, since I need it or care about it, but that’s just me. Also, add a user here that’s preferably the same username as who’s going to be shelling up to the server. For example, let’s say your application will run on domain name “codemonkey.com” (no affiliation). Its SSH/FTP username may be “cmonkey”. Make that your user, and try to make his password the same as it’s going to be for SSH/FTP.
After this is done, you should have a code repository on your system that you can now do some imports. Onward!
Importing Your Code Into Subversion
Up until now, your code has probably only existed on your development machine, and/or your backup drive. No big deal - we’re about to bring it into subversion.
[you@your_local_machine]$ svn import /path/to/my/application/
svn://yourdomain.com/yourcodebase/appname/trunk
Obviously, replace the above paths with the appropriate equivalent for your situation. There’s one important thing I want to point out here about directories though:
Make sure that when you import into the trunk/ directory remotely, that what you import is the root directory of your rails application. In other words, it should be adding app/, config/, lib/, db/, and so on, NOT myapp/app, myapp/config, myapp/lib, myapp/db, and so on.
Note that before you do this, you can do some property settings on certain files (like log/development.log) to prevent them from going up to the server or being managed. Refer to the Subversion documentation for exactly how to do that. Personally, I just leave everything as-is.
Making Preparations - Server Side
Now it’s time to shell up to the server and make a few directories, and change some permissions.
First we need a directory where the rails application will reside. Let’s say the application is named “codemonkey”.
[root@yourserver ~] cd /var/www/vhosts/codemonkey.com/
[root@yourserver codemonkey.com] mkdir cmonkey
[root@yourserver codemonkey.com] chown cmonkey.psaserv cmonkey
That creates the directory that we’ll be putting all our stuff in, and changes the ownership to the “cmonkey” user, and the “psaserv” group.
Next, we configure Capistrano.
Setting Up Capistrano
Capistrano (cap for short) is a tool developed by Jamis Buck to help with Rails deployments. It’s kind of tough to master, but once you get a handle on it, you’ll really like it a lot.
First, you need to “capify” your application:
[you@your_local_machine myapp]$ capify .
This generates a couple files. First, edit config/deploy.rb. Your default configured file will look much different than what I have pasted below, but what I have here is what you need. Make changes to directories and paths as needed.
set :application, "your_application_name"
set :repository, "svn://yourdomain.com/your_codebase/your_app/trunk"
# the :repository variable tells Capistrano where your code to deploy
# lives. It needs to know so it can check out the appropriate code.
# If you aren't deploying to /u/apps/#{application} on the target
# servers (which is the default), you can specify the actual location
# via the :deploy_to variable:
set :deploy_to, "/var/www/vhosts/your-app-domain-name.com/#{application}"
# If you aren't using Subversion to manage your source code, specify
# your SCM below:
# set :scm, :subversion
role :app, "codemonkey.com"
role :web, "codemonkey.com"
role :db, "codemonkey.com", :primary => true
# For now, just make all these the same. In more complex deployments,
# these are used to assign different servers for the web front-end,
# the application itself, and the DB.
# SCM
set :scm_username, 'cmonkey'
# Make this whatever username is going to access Subversion.
set :scm_password, proc{Capistrano::CLI.password_prompt('SVN Password:')}
# This line will prompt you when running cap for the password
# for the above user.
# Other options
set :user, 'cmonkey' # the user on the server who will be connecting via ssh
set :deploy_via, :export
set :runner, 'cmonkey' # this is who will be running the processes for rails.
# don’t ever make this root, apache, or nobody!
Make the necessary changes to this configuration then save it as deploy.rb in the config/ directory.
Creating a Spinner
This is one thing that really got me at first. You have to create a specific file that tells Capistrano how to start your rails application.
Create a file called “spin” (not spin.rb, just “spin”) in the script/ directory. So its final location will be:
yourapp/script/spin
This file needs to contain the following:
/var/www/vhosts/your-app-domain-name.com/your-app/
current/script/process/spawner -p 11000 -i 3
(All of that should be on one line. It's broken
down here for layout purposes.)
There are a few things here you need to configure based on your expectations for your application:
- -p 11000: this says “run it on port 11000”. If you have something running on that port already, you’ll need to specify something else. Any port that doesn’t have an application running on it or bound to it already should be fine. I’d say use something above at least 5000 just for safety’s sake.
- -i 3: This tells Capistrano to launch 3 processes in tandem. It’ll fire up three Mongrel processes (mongrel is the default, by the way - you can specify fcgi, but that’s not something I’m covering here), each of which can run and serve your application.
About Mongrel, Processes and Memory
You need to know this, so don’t just skim this section. The spawner script will launch Mongrel by default, and Mongrel is a very fast server implemented in both C and Ruby, but it has a catch: it’s not multi-threaded. In other words, unlike Apache, it can only serve one request at a time. If Apache gets a ton of requests all at once, it’ll automatically “fork” itself (duplicates itself) to handle the requests. Mongrel doesn’t do this, which is why we have to launch from one to five instances of it (one being the minimum, five being an absolutely huge site). I recommend two or three processes to start out (specified with the -i # option above).
Each Mongrel process can consume somewhere between 40 and 100 megs of memory, so if you’re running 3 processes on a large application, that’s 300 megs of memory gone right there. It doesn’t throttle memory based on usage or load either - it’s eaten up completely. As always, YMMV, but be stingy here. Pound and Apache will help us shuttle requests very well, so we don’t need a whole ton of these things running.
Add New Files to Subversion
We just created some new config files, so we need to go and add these to Subversion. It can’t manage what it doesn’t know about, so do the following:
[you@your_local_machine myapp]$ svn add config/deploy.rb
[you@your_local_machine myapp]$ svn add script/spin
Next, you need to commit the changes to Subversion. This copies those two files we just added to the SVN repository:
[you@your_local_machine myapp]$ svn commit . -m
“Created deploy.rb and spin for deployment”
(The -m “blah blah blah” adds a note that says what you did, or what changes were submitted. It’s not optional, so make sure that it’s in there. If you get an error about being unable to launch an editor, that’s because you forgot the -m and a message.)
Setting Up Your Database
I’m assuming you’re going to use MySQL for your database here. First, login to Plesk and create a new database, and a new user and assign that user a password. Write that all down.
Now, go into config/database.yml and edit the production settings to match those. Then add this file to subversion (as above), and commit again. For example:
[you@your_local_machine myapp]$ svn add config/database.yml
[you@your_local_machine myapp]$ svn commit . -m “Updated database.yml”
Note: I ran into a problem with RHEL here that I didn’t know about until deployment. Rails wasn’t able to find the MySQL socket on its own, so I had to add the line:
socket: /var/lib/mysql/mysql.sock
To config/database.yml in order for it to work. Bear in mind that this is relative to your production machine; don’t tell it where the socket is on your local development box, but on the deployment server.
Making script/* executable
Another potential “gotcha” is that script/* needs to be set as an executable script. Do this with subversion by executing the following command:
[you@your_local_machine myapp]$ svn propset svn:executable "*"
`find script -type f | grep -v '.svn'`
Cold Deploy (i.e. First Deploy)
It’s about time right? First, we need Capistrano to create the appropriate directory structure:
[you@your_local_machine myapp]$ cap deploy:setup
This will have Capistrano ask you for your shell password, then go into the server as that user and create the directories required under where you said :deploy_to. Next, it’s time to do a COLD DEPLOY.
[you@your_local_machine myapp]$ cap deploy:cold
This will shell into the server, check out your source, put it in the appropriate directory, run your database migrations, and start Mongrel processes.
Congratulations! If you go to www.yourdomain.com:11000 (or whatever port you specified), you should see your application running nice and pretty.
Oh Sh*t! I forgot something!
Okay so maybe you forgot that one last little tweak you needed to your code. No big deal. Go make your changes, fire up your local script/server and test it out (or write tests if it’s something big), then save all your work.
Capistrano is great about re-deploying after you’ve made changes. The basic process for publishing a new “version” of your application is this:
- Write the changes, test, get ‘em ready
- Commit to Subversion with changelog
- Run “cap deploy”
So after you’ve finished your changes and you know they’re production ready, do the following:
[you@your_local_machine myapp]$ svn commit . -m
“What did you change? Type it here.”
[you@your_local_machine myapp]$ cap deploy
Capistrano does all the heavy lifting for you - puts the new files in place, re-links the new release directory, and restarts all your Mongrel processes. Not bad eh?
Installing PCRE and Pound
Now we’re going to put a load balancer in place. Remember what I said about Mongrel not being multi-threaded? We need something that can say, “okay this process is overloaded, so send it to this other one” automatically for us. That’s Pound.
The Pound Website:
Pound
But before we can get Pound running right, we need PCRE, the Perl-Compatible Regular Expression library. Check out:
PCRE
You’ll want to shell up to the server and download (I recommend copying the URL, then using wget) the latest source version (which is 7.5 at the time of this writing). Unpack it, and then just:
[user@your-server pcre-version]$ ./configure
[user@your-server pcre-version]$ make
[user@your-server pcre-version]$ sudo make install
You now have PCRE updated. Now it’s time to get Pound rocking.
Download the Pound source code then unpack it. First you need to make a quick edit to one of the C code files. It’s no big deal though. Pop open nano on http.c and hit ctrl+w and find the text, “X-Forwarded-For”. Now, comment the following two lines:
//addr2str(caddr, MAXBUF - 1, &from_host);
//BIO_printf(be, "X-Forwarded-For: %s\r\n", caddr);
This is because if you don’t, it can screw things up a little bit (credit: peter-de-berdt from forums.rimuhosting.com).
Now, run the following commands:
[user@your-server pcre-version]$ ./configure --with-ssl=ssl_dir
[user@your-server pcre-version]$ env LD_LOAD_PATH=/usr/local/lib make
[user@your-server pcre-version]$ sudo make install
The second command is crucial, because without it, Pound can’t find PCRE (again, according to the forum user mentioned above).
Configuring Pound
Next, edit
/etc/default/pound
and insert the following line:
startup=1
For me, that file didn’t exist, so go ahead and create it. I’m not sure if this is all that’s needed to ensure that Pound loads on system reboot or not, so if somebody has more experience here, please let us know.
Now, you need to edit the following file:
[user@your-server ~]$ sudo nano /etc/init.d/pound
Add this line:
DAEMON=/usr/local/sbin/pound
Save that file. Next, we need to create a symbolic link between a few files:
[user@your-server ~]$ sudo ln -s /usr/local/etc/pound.cfg
/etc/pound/pound.cfg
Okay, we now have Pound set up for everything except the application itself. Now, crack open
/usr/local/etc/pound.cfg
and make it look similar to this:
User "nobody"
Group "nobody"
LogLevel 1
Alive 30
ListenHTTP
Address 0.0.0.0
Port 9632
xHTTP 1
Service
HeadRequire "Host: .*yourdomain.com.*"
BackEnd
Address 127.0.0.1
Port 11000
End
BackEnd
Address 127.0.0.1
Port 11001
End
BackEnd
Address 127.0.0.1
Port 11002
End
Session
Type BASIC
TTL 300
End
End
End
You’ll want to change the user and group to be something more appropriate to your system, most likely, as well as “yourdomain.com” to whatever domain name you’re running your rails application on.
Also, you need a BackEnd section for each process and port you’re running Mongrel on. So in my example, we have three Mongrel processes running on ports 11000, 11001, and 11002. I have a BackEnd section for each of those.
For your next rails application on this server, just edit this file again and add a new “Service” section that’s identical to the first, except for the domain and port numbers, then restart Pound.
Q: “Why port 9632?”
I chose this port for my configuration because I already had Tomcat running on 8080 and wanted something real off-the-wall and unusual. You can put anything you want here, as long as nothing else is bound to that port already.
Last Step: Apache
We’re almost finished! Now we’re going to tie Apache into the mix so that users can simply go to www.yourdomain.com, not www.yourdomain.com:9632.
This is where we have to really deal within Plesk’s world, so start by editing:
/var/www/vhosts/yourdomain.com/conf/vhost.conf
The file may not yet exist, so go ahead and create it. Whatever we put in this file is automatically included in Apache’s
I have here a sample configuration file. If you copy-pasta this, make sure you change things to reflect your installation, not mine. I’ve tried to add comments where appropriate to help you along.
DocumentRoot /var/www/vhosts/mydomain.com/myapp/current/public
# This needs to be inside your :deploy_to/current/public from deploy.rb
ServerName yourdomain.com
ServerAlias www.yourdomain.com
# Just some naming stuff
# ProxyPass for Rails to Pound (which load-balances for Mongrel)
ProxyRequests Off
ProxyPassReverse / http://www.yourdomain.com:9632/
# Replace 9632 with whatever port number Pound is running on. This is
# how we have Apache forward stuff to Pound.
ProxyPreserveHost on
<Directory "/var/www/vhosts/yourdomain.com/yourapp/current/public">
Options FollowSymLinks
AllowOverride None
Order allow,deny
Allow from all
</Directory>
RewriteEngine On
# Check for maintenance file. Let apache load it if it exists
RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f
RewriteRule . /system/maintenance.html [L]
# Let apache serve static files
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f
RewriteRule (.*) $1 [L]
# Don't do forward proxying
ProxyRequests Off
RewriteRule ^/error/.* /usr/share/apache2/$0 [L]
# Enable reverse proxying
<Proxy *>
Order deny,allow
Allow from all
</Proxy>
# Route the plesk-stats through Apache instead of proxying them to
# Pound and Mongrel cluster
RewriteRule ^/plesk-stat/?(.*)
/var/www/vhosts/railsappdomain1.com/statistics/$1 [L]
RewriteRule ^/(webstat|webstat-ssl|ftpstat|anon_ftpstat)/?.*$
/var/www/vhosts/railsappdomain1.com/statistics$0 [L]
RewriteRule ^/awstats-icon/(.*) /usr/share/awstats/icon/$1 [L]
# Serve static files through Apache
RewriteRule ^/(images|stylesheets|javascripts|system)/?(.*) $0 [L]
# Send the rest to Pound
RewriteRule ^/(.*)$ http://localhost:9632/$1 [P,L]
# Rewrite index to check for static
RewriteRule ^/$ /index.html [QSA]
# Rewrite to check for Rails cached page
RewriteRule ^([^.]+)$ $1.html [QSA]
# Redirect all non-static requests to cluster
RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
# Deflate all text based files for faster transfer to the client
AddOutputFilterByType DEFLATE text/html text/plain text/css text/javascript
# ... text/xml application/xml application/xhtml+xml text/javascript
BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4.0[678] no-gzip
BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
That’s a lot of stuff, but in my situation, it works flawlessly. Go ahead and save that file, then do the following:
[user@your-server ~]$ su - root
<You’ll type your password...>
[root@your-server ~]# /usr/local/psa/admin/bin/websrvmng -av
[root@your-server ~]# service httpd configtest
<You should see, “Syntax OK”>
[root@your-server ~]# service httpd reload
Reloading httpd: [ OK ]
If you get errors with your Apache vhost.conf configuration, go back and check that out, then re-run your configtest. Always run a configtest BEFORE reloading or restarting your webserver!
Your application should now be fully deployed and available at www.yourdomain.com. Congratulations!