Puma is a fast multi-threaded Ruby app server designed to host rack-based Ruby web apps including Sinatra and Ruby on Rails. Like Unicorn, it supports rolling restarts, but since it is multi-threaded rather than Unicorn’s multi-process model, it takes far less memory while being comparable in performance. Puma can run on Ruby 1.9.X but its multi-threaded nature is better suited to run on a real multi-threaded runtime like Rubinius or JRuby.

This article will guide you to setting up a hello world Rails app with Puma/Nginx and deploy it with Capistrano onto a linux system. This guide was tested on Puma 1.6.3 and Puma 2.0.1.

Create a base Rails app

rails new appname

Adding Puma to Rails app

We’ll start with adding a puma to your Rails app.

In your Gemfile , add:

gem "puma"

then run bundle install

Now we need a puma config file: config/puma.rb

rails_env = ENV['RAILS_ENV'] || 'development' threads 4,4 bind "unix:///data/apps/appname/shared/tmp/puma/appname-puma.sock" pidfile "/data/apps/appname/current/tmp/puma/pid" state_path "/data/apps/appname/current/tmp/puma/state" activate_control_app

Setup Nginx with Puma

Follow the instructions to install Nginx from source. It will install nginx to /usr/local/nginx

Edit your /usr/local/nginx/conf/nginx.conf file to be below:

user deploy; worker_processes 1; error_log /var/log/nginx/error.log; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /usr/local/nginx/conf/mime.types; default_type application/octet-stream; access_log /var/log/nginx/access.log; sendfile on; #tcp_nopush on; #keepalive_timeout 0; keepalive_timeout 65; tcp_nodelay on; gzip on; server_names_hash_bucket_size 128; client_max_body_size 4M; client_body_buffer_size 128k; include /usr/local/nginx/conf/conf.d/*.conf; include /usr/local/nginx/conf/sites-enabled/*; }

Create a file named “puma_app” in the sites-enabled directory:

upstream appname { server unix:///data/apps/appname/shared/tmp/puma/appname-puma.sock; } server { listen 80; server_name www.appname.com appname.com; keepalive_timeout 5; root /data/apps/appname/public; access_log /data/log/nginx/nginx.access.log; error_log /data/log/nginx/nginx.error.log info; if (-f $document_root/maintenance.html) { rewrite ^(.*)$ /maintenance.html last; break; } location ~ ^/(assets)/ { root /data/apps/appname/current/public; expires max; add_header Cache-Control public; } location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; if (-f $request_filename) { break; } if (-f $request_filename/index.html) { rewrite (.*) $1/index.html break; } if (-f $request_filename.html) { rewrite (.*) $1.html break; } if (!-f $request_filename) { proxy_pass http://appname; break; } } # Now this supposedly should work as it gets the filenames # with querystrings that Rails provides. # BUT there's a chance it could break the ajax calls. location ~* \.(ico|css|gif|jpe?g|png)(\?[0-9]+)?$ { expires max; break; } location ~ ^/javascripts/.*\.js(\?[0-9]+)?$ { expires max; break; } # Error pages # error_page 500 502 503 504 /500.html; location = /500.html { root /data/apps/appname/current/public; } }

Using init scripts to start/stop/restart puma

We want to be able to start/restart puma using linux init scripts.

The init scripts for nginx should have been installed already as well. You can start nginx using ` sudo /etc/init.d/nginx start

Install Jungle from Puma’s source repo. Jungle is a set of scripts to manage multiple apps running on Puma. You need the puma and run-puma files and place them into /etc/init.d/puma and /usr/local/bin/run-puma respectively.

Then, add your app config using: sudo /etc/init.d/puma add /data/apps/appname/current deploy

IMPORTANT: The init script comes with an assumption that your puma state directories live in /path/to/app/tmp/puma

Using Capistrano to deploy

In your Gemfile , add:

gem "capistrano"

then run bundle install

In your deploy.rb, change to the following below. Note the shared tmp dir modification.

#======================== #CONFIG #======================== set :application, "APP_NAME" set :scm, :git set :repository, "GIT_URL" set :branch, "master" set :ssh_options, { :forward_agent => true } set :stage, :production set :user, "deploy" set :use_sudo, false set :runner, "deploy" set :deploy_to, "/data/apps/#{application}" set :app_server, :puma set :domain, "DOMAIN_URL" #======================== #ROLES #======================== role :app, domain role :web, domain role :db, domain, :primary => true #======================== #CUSTOM #======================== namespace :puma do desc "Start Puma" task :start, :except => { :no_release => true } do run "sudo /etc/init.d/puma start #{application}" end after "deploy:start", "puma:start" desc "Stop Puma" task :stop, :except => { :no_release => true } do run "sudo /etc/init.d/puma stop #{application}" end after "deploy:stop", "puma:stop" desc "Restart Puma" task :restart, roles: :app do run "sudo /etc/init.d/puma restart #{application}" end after "deploy:restart", "puma:restart" desc "create a shared tmp dir for puma state files" task :after_symlink, roles: :app do run "sudo rm -rf #{release_path}/tmp" run "ln -s #{shared_path}/tmp #{release_path}/tmp" end after "deploy:create_symlink", "puma:after_symlink" end

You’ll need to setup the directories one time using: cap deploy:setup

Now you can deploy your app using cap deploy and restart with cap deploy:restart .

EDIT: xijo has formalized the cap tasks as a gem, capistrano-puma to make it easier to use on multiple projects.