Serving Rails apps with Unicorn and Bluepill. Includes Resque.

At Wordtracker we run four Rails apps in production. Two of those use background jobs with a Resque implementation. We've recently switched to serving them using Unicorn, with Nginx as a proxy. We've found the restart times on deployment to be far quicker and more graceful, which gives us confidence to roll out new features to our customers as soon as they become available.

I hope the following configuration snippets might be helpful for anyone wanting to serve their Rails applications using Unicorn.

For the record, we're currently on Rails 3.0.7 with Ruby 1.9.2.

The forking blocks feel like code, rather than configuration, but they are extremely important if you're running with multiple worker processes:

    pid_path = "tmp/pids/unicorn.pid"
    listen "*:8031"
    worker_processes 8
    timeout 30
    preload_app true
    pid pid_path
    stderr_path "log/unicorn-err.log"
    stdout_path "log/unicorn-out.log"

    before_fork do |server, worker|
      ActiveRecord::Base.connection.disconnect!
      old_pid_path = "#{pid_path}.oldbin"
      if File.exists?(old_pid_path) && server.pid != old_pid_path
        begin
          Process.kill("QUIT", File.read(old_pid_path).to_i)
        rescue Errno::ENOENT, Errno::ESRCH
          # someone else did our job for us
        end
      end
    end

    after_fork do |server, worker|
      ActiveRecord::Base.establish_connection
      rails_env = ENV['RAILS_ENV'] || 'production'
      worker.user('app', 'app') if Process.euid == 0 && rails_env == 'production'
    end

We need to monitor our apps together with their Resque background jobs. We're using Bluepill for that:

    app_name = 'my-app'
    worker_queues = ['my-app'] * 4

    rails_env = 'production'
    user = 'app'
    group = 'app'
    rails_root = "/var/apps/my-app/current"
    ENV['PATH'] = "/opt/ruby192/bin:#{ENV['PATH']}"

    Bluepill.application(app_name) do |app|

      app.working_dir = rails_root
      app.uid = user
      app.gid = group

      app.process("unicorn") do |process|
        process.pid_file = "#{rails_root}/tmp/pids/unicorn.pid"
        process.stdout = process.stderr = "#{rails_root}/log/bluepill.log"
        process.environment = { 'RAILS_ENV' => rails_env }

        # Unicorn needs to be invoked using a path which includes 'current', 
        # otherwise it tries to restart with the executable installed in an old release dir (which get cleaned)
        process.start_command = "bundle exec #{rails_root}/vendor/bundle/ruby/1.9.1/bin/unicorn -Dc unicorn.rb"
        process.stop_command = "kill -QUIT {{PID}}"
        process.restart_command = "kill -USR2 {{PID}}"

        process.start_grace_time = 30.seconds
        process.stop_grace_time = 5.seconds
        process.restart_grace_time = 13.seconds
      end

      if defined?(worker_queues)
        worker_queues.each_with_index do |queue_list, i|
          app.process("resque-#{i}") do |process|
            process.group = "resque"

            process.pid_file = "#{rails_root}/tmp/pids/resque-#{i}.pid"
            process.stdout = process.stderr = "#{rails_root}/log/resque-#{i}.log"
            process.environment = {
              'RAILS_ENV' => rails_env, 
              'QUEUE' => queue_list
            }
            process.start_command = "bundle exec rake resque:work"
            process.stop_command = "kill -QUIT {{PID}}"
            process.daemonize = true
          end
        end
      end
    end

Finally, a bit of Nginx config

    root /var/wordtracker/apps/my-app/current/public;
    access_log /var/log/nginx/my-app.access.log main;

    location / {
      try_files $uri @unicorn;
      access_log off;
      expires 30d;
    }
    location @unicorn {
      proxy_pass http://localhost:8031;
      error_log /var/log/nginx/my-app.error.log warn;
      include /etc/nginx/nginx-proxy.conf;
      proxy_set_header X-Forwarded-Proto https;
    }