Server Maintenance Mode for Rails, Capistrano and Apache2

It's a good idea to have a server maintenance strategy in place during the early phases of any project.  Along with CI, provisioning multiple stages, and an efficient way to deploy new features to the client and eventually to production, a strategy for server maintenance from the outset of the project will save time as the application ages.

If your Rails app is already in the wild, no worries.  Here are a few easy steps to modify Apache to detect a maintenance mode and a Capistrano task to turn it on and off.

First, you'll need to add these lines to you Apache virtual host configuration:

 RewriteEngine On
ErrorDocument 503 /system/maintenance.html
RewriteCond %{REQUEST_URI} !.(css|gif|jpg|png)$
RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f
RewriteCond %{SCRIPT_FILENAME} !maintenance.html
RewriteRule ^.*$ - [L,R=503]

This tells Apache to bypass the app when it detects the presence of a maintenance file.  Instead of serving the request, it will return the maintenance page with the status code 503.  If Apache is already running, you'll want to restart it before you try to roll it into maintenance mode.

Next, you'll want to create a maintenance page for your app.  A good place to put the file is /public/maintenance.html.  Remember, the page will bypass Rails completely, so make sure it's a fully valid HTML file (although the rewrite rules will still serve CSS and image files).

Last, add two Capistrano tasks to turn maintenance mode on and off.  The first copies the maintenance page to the place where Apache will start serving it.  The second will remove it.  Add these lines to deploy.rb:

 namespace :deploy
 namespace :web do
 desc "Enable maintenance mode for apache"
 task :disable, :roles => :web do
 on_rollback { run "rm -f #{shared_path}/system/maintenance.html" }
 page ='public/maintenance.html')
 put page, "#{shared_path}/system/maintenance.html", :mode => 0644

 desc "Disable maintenance mode for apache"
 task :enable, :roles => :web do
 run "rm -f #{shared_path}/system/maintenance.html"

Now when you want to set a stage into maintenance mode (integration in this example), just run the task:

 cap integration deploy:web:disable

To disable maintenance mode, run:

 cap integration deploy:web:enable

And there you have it: maintenance mode, easy mode.

A few caveats:

  1.  Make sure the shared path for Capistrano has a system directory.  Otherwise, you'll get an error writing the maintenance file there.
  2. Be careful if there is a load balancer in front of your app servers.  It might be configured NOT to serve 503s, but instead start ignoring that app server.

A few links I used to write this post:

  1. Stack Overflow post on the cap task for maintenance mode.
  2. Gist for the suggested Capistrano maintenance check.
  3. The Capistrano tasks used to exist but have since been removed.  This gem will add them back.
Ryan Foster

Posted in Article Category: #Code