"Better Rails production box"

September 26, 2011

Here are 3 simple things to do in order to improve deployment process of your Rails application and friendliness of your production server. Nothing new here but many good practices are often overlooked even by experienced developers.

Set RAILS_ENV in .bash_profile

I see my fellow devs typing this on production server all the time:

script/rails console production

Or this:

RAILS_ENV=production rake some_task

It's obvious that on production box you want it to run in production environment, right? Set RAILS_ENV in .bash_profile and save your fingers:

# ~/.bash_profile
export RAILS_ENV="production"

This might not be the case if your hosting service is like Heroku or similar as they export RAILS_ENV for you automatically. But if you're on self-managed VPS or EC2 instance you can ease your work with this trivial setup.

Use logrotate

Create /etc/logrotate.d/rails_logs file with following content:

/var/www/*/shared/log/*.log {
  daily
  missingok
  rotate 30
  compress
  delaycompress
  copytruncate
}

That will tell logrotate to rotate log files daily, compress them, keep last 30 days and don't choke when file is missing. copytruncate is important here as it will make sure log file currently used by Rails app is not moved but truncated. That way the app can just keep on logging without reopening log file.

Don't forget about this one if you manage production box yourself. And do it when you initially setup the box, not "later". "Later" often means "when app is down due to not enough disk space". Srsly.

Use maintenance page

When deploying with long running or non-trivial (more than add column) migrations you should use maintenance page of some sort. With capistrano you can just use (before running migrations):

cap production deploy:web:disable

and (after they finish):

cap production deploy:web:enable

Default maintenance page put by capistrano is kind of ugly so you should make your own matching your site design. In order to do this prepare app/views/layouts/maintenance.html.erb and override deploy:web:disable task in config/deploy.rb:

namespace :deploy do
  namespace :web do
    task :disable, :roles => :web do
      require 'erb'
      on_rollback { run "rm #{shared_path}/system/maintenance.html" }

      reason = ENV['REASON']
      deadline = ENV['UNTIL']
      template = File.read('app/views/layouts/maintenance.html.erb')
      page = ERB.new(template).result(binding)

      put page, "#{shared_path}/system/maintenance.html", :mode => 0644
    end
  end
end

That will put your custom page in #{shared_path}/system/maintenance.html (also accessible via public/system/maintenance.html by webserver).

On the other end, you should configure webserver to respect presence of this file. Here is config snippet for Nginx:

server {
  listen 80;
  server_name example.org;

  ...

  # Maintenance page support

  set $maintenance 0;

  if (-f $document_root/system/maintenance.html) {
    set $maintenance 1;
  }

  if ($request_uri ~* (jpg|jpeg|gif|png|js|css)$) {
    set $maintenance 0;
  }

  if ($maintenance) {
    return 503;
  }

  error_page 503 @maintenance;
  location @maintenance {
    rewrite ^(.*)$ /system/maintenance.html break;
  }
}

It will make sure that when maintenance.html file is there Nginx will serve it and return 503 (Service unavailable) HTTP status. Proper response status is important here as it gives search engines the message: don't index me now please, come back later. You don't want your maintenance page get indexed, do you?

Above Nginx config also allows assets to be served as usual when maintenance mode is on - especially useful if you don't host them on separate host/subdomain and you want the page to look nice and match presence of your full site.

blog comments powered by Disqus