Tutorial

How To Set Up Zero Downtime Rails Deploys Using Puma and Foreman

Published on September 10, 2013
Default avatar

By Alex Ghiculescu

How To Set Up Zero Downtime Rails Deploys Using Puma and Foreman

Introduction

Puma is an efficient Ruby web server that handles Rack apps (such as Rails) very well. Puma offers concurrency using threads and/or workers. You can use Puma’s clustered mode (using workers) to deploy apps without downtime. Puma will restart workers one by one, other workers will continue to handle during this time. This is good because your users will no longer see delayed responses or error pages when you deploy updates to your app.

In this guide, we are going to use the following:

  • Puma as our web server
  • Foreman to manage our app (this is not strictly necessary but it does make life a bit easier)
  • Capistrano to deploy (the deployment instructions are fairly generic and can easily be re-applied to other methods)

This guide assumes you have an existing Rails app you’ll be using. See the Rails Getting Started guide if you’re not up to there yet. This guide also assumes you are using Ubuntu; read up on how to set it up here. We will be using Upstart in this guide.

Installing Puma

Puma is installable via RubyGems:

gem install puma

Once installed, you can launch an app using Puma simply by calling:

puma config.ru # or whatever your *.ru file is called

But instead, we’re going to use Foreman to manage our app. Read on.

Installing Foreman

Foreman can be installed using RubyGems:

gem install foreman

Configuring Rails with Puma and Foreman

First, add puma and foreman to your Gemfile:

# Gemfile
gem 'puma'
gem 'foreman'

And install:

bundle install

Foreman requires a Procfile which is used to specify processes to run. You can give processes names, for example web and worker, which you can then use to distinguish between which types of processes to run. The next thing we will do is create our Procfile:

echo "web: bundle exec puma -e $RAILS_ENV -p 5000 -S ~/puma -C config/puma.rb" >> Procfile

You can change the port number to anything you like, we’ll use 5000. Next, create a puma config file and edit it to resemble this:

# config/puma.rb
threads 1, 6
workers 2

on_worker_boot do
  require "active_record"
  cwd = File.dirname(__FILE__)+"/.."
  ActiveRecord::Base.connection.disconnect! rescue ActiveRecord::ConnectionNotEstablished
  ActiveRecord::Base.establish_connection(ENV["DATABASE_URL"] || YAML.load_file("#{cwd}/config/database.yml")[ENV["RAILS_ENV"]])
end

It is important to configure the number of threads and workers correctly, and the Puma README offers some advice on how best to do this. To run Puma in clustered mode, you will need at least two workers. Generally you should match the number of cores available on your VPS. On Ubuntu you can use the command:

grep -c processor /proc/cpuinfo

to check how many cores you have. Alternatively, you can tell based on the type of droplet you are using. Note, also, that each worker will use the given number of threads, so with the configuration above, there will be a minimum of 2 and and a maximum of 12 threads.

The on_worker_boot block establishes ActiveRecord connections whenever a worker is booted (ie. during a deploy). If you are using a different ORM you will want to make the appropriate connections here.

Once you have configured your Procfile and puma.rb you should be able to run your application. To do this, simply run the command:

foreman start

Once loaded your app should be available at localhost:5000 (or whichever port you specified in your Procfile).

Deployments

Foreman is able to export to other process management formats; we are going to use Upstart, which is the Ubuntu process manager. You can export Ubstart-compatible scripts using Foreman by running:

sudo foreman export upstart /etc/init -a puma-test-app -u puma-test-user -l /var/puma-test-app/log

In this example, puma-test-app and puma-test-user would be replaced by the appropriate app name and system user. We don’t want to do this locally, though - what we want to do is automatically create upstart scripts as part of our deployment, so that we are always launching the app correctly and letting Upstart ensure it keeps running.

We’ll use Capistrano to deploy. If you haven’t already done so, set up your app to work with Capistrano by running:

capify .

in your app’s root directory. Next, in the file created at config/deploy.rb, add the following:

# config/deploy.rb
set :app_name, "puma-test-app"
set :user, "puma-test-user"

namespace :foreman do
  desc "Export the Procfile to Ubuntu's upstart scripts"
  task :export, :roles => :app do
    run "cd #{current_path} && #{sudo} foreman export upstart /etc/init -a #{app_name} -u #{user} -l /var/#{app_name}/log"
  end
  
  desc "Start the application services"
  task :start, :roles => :app do
    run "#{sudo} service #{app_name} start"
  end
 
  desc "Stop the application services"
  task :stop, :roles => :app do
    run "#{sudo} service #{app_name} stop"
  end
 
  desc "Restart the application services"
  task :restart, :roles => :app do
    run "#{sudo} service #{app_name} start || #{sudo} service #{app_name} restart"
  end
end

The foreman:export task will update Ubuntu’s Upstart scripts, we will call this whenever we deploy. The other tasks manage the Upstart-backed service. Your Procfile will not change regularly, but if it does, you’ll need to restart the application service to register these changes; you can do now do this by running:

cap foreman:restart

However, in the majority of cases, all we will want to do is tell Puma to do a phased restart. To do this, we are going to use the Capistrano deploy:restart task, which is run automatically as part of a standard Capistrano deployment. Add the following to your config/deploy.rb:

namespace :deploy do
  task :restart, :roles => :app do
    foreman.export

    # on OS X the equivalent pid-finding command is `ps | grep '/puma' | head -n 1 | awk {'print $1'}`
    run "(kill -s SIGUSR1 $(ps -C ruby -F | grep '/puma' | awk {'print $2'})) || #{sudo} service #{app_name} restart"

    # foreman.restart # uncomment this (and comment line above) if we need to read changes to the procfile
  end
end

The first thing the deploy:restart task does is call foreman:export to update our Upstart scripts. Then, we send the SIGUSR1 signal to all running instances of Puma. It does this by finding the process ID for the Puma master process, then sending it the appropriate signal. The commented out command finds the PID on OS X, it may be helpful to keep both on hand in case you need to test locally. When Puma receives the SIGUSR1 signal it will, commence a phased restart. If we are unable to send the SIGUSR1 signal for whatever reason, we fall back to restarting the service the old fashioned way.

If you need to register changes to your Procfile then you will have to call the foreman:restart task. To this, comment out the line that starts with run, and uncomment the foreman.restart line. You shouldn’t need to do this regularly as ideally your Procfile would remain the same between deployments.

Testing

Before testing, make sure Foreman is installed on your Ubuntu VPS using the instructions above.

If you haven’t already, you’ll need to set up your VPS to work with Capistrano. First, fill in the blanks in your config.deploy.rb for your web server, repository, etc. Then configure your VPS by running:

cap deploy:setup

You can then run:

cap deploy

to do your first deployment. During this deploy, your app will be started as an Ubuntu service on your VPS. In all subsequent deployments, Puma will get sent the SIGUSR1 signal which will trigger a phased restart; you should be able to continue using your app throughout the restart process.

Database migrations

While Puma does a phased restart, your app will be running two different codebases. Thus, you will need to ensure that both codebases work with your existing database schema, which may be tricky if you have to make migrations. (If you have a substantial migration to make and need to take the site offline, you can do so by calling your new foreman:stop Capistrano task, then calling the foreman:start task to go back online later.)

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about us


About the authors
Default avatar
Alex Ghiculescu

author

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
9 Comments


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Later versions of Puma ship with the pumactl, which makes restarting a breeze:

In the Puma config file, add a pidfile directive:

pidfile File.expand_path("../../tmp/pids/puma.pid", __FILE__)

And now use pumactl in your Capistrano configuration:

run "bundle exec pumactl --pidfile #{current_path.join("tmp/pids/puma.pid")} phased-restart

Or in my case, for Capistrano 3, I’ve used the following:

fetch(:bundle_bins, []).push 'foreman', 'pumactl'

namespace :deploy do
  task :restart do
    on roles(:app) do
      within release_path do
        pidfile = current_path.join('tmp/pids/puma.pid')
        if test "[ -f #{pidfile} ]"
          execute :pumactl, '--pidfile', pidfile, 'phased-restart'
        else
          invoke 'foreman:restart'
        end
      end
    end
  end
Andrew SB
DigitalOcean Employee
DigitalOcean Employee badge
June 10, 2014

@vidhya.kumar: If you are trying to call the upstart script directly, it would be:

<pre> sudo service app_name start </pre>

I can’t seem to make the upstart script doesn’t seem to work for me. When I go ‘my app start’ the web server doesn’t start but it works when I go ‘foreman start’. Any idea?

I’m having an issue - this works great, until I restart the server (Ubuntu 12.04). After restart I do a ps aux | grep puma I see all the puma workers with STAT = R, and they never get to STAT= S. Nginx gives me a connection refused, socket busy error. Any ideas?

To find puma pid you can add this to puma config pidfile ‘/tmp/puma.pid’

and call phased restart more gracefully kill -s SIGUSR1 $(cat #tmp/puma.pid)

Following this article, Foreman seems to eat up the CPU. I’m showing that there are two puma processes, and one of them just keep throttling. Remove foreman, and it works fine. Any ideas?

Note that if you are using bundler, capistrano and your gemfile changes, you are likely to observe this behaviour: https://github.com/puma/puma/issues/300

You then need to do a full restart of your puma server.

If you run into a TTY error w/ Ubuntu 12.04, you may also need to add:

default_run_options[:pty] = true

This can go in your deploy.rb (recommended) or in your ~/.caprc

To send the SIGUSR1 from Capistrano you need to ensure it’s running from bash. Otherwise you get the following error:

invalid signal number or name: SIGUSR1

Fix it by adding this to deploy.rb:

default_run_options[:shell] = ‘/bin/bash’

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more
DigitalOcean Cloud Control Panel