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.
Nice article!
Rather than have all those if statements in the / location block, it’s best-practice with Nginx to use ‘try_files’. http://wiki.nginx.org/IfIsEvil
thanks for the tip! i’ll take a look at this.
Do you use anything to monitor you Puma processes?
I haven’t set it up for my puma process, but I typically use monit to restart a killed process and pingdom to ensure the website is externally accessible.
I use Monit also so I just forgo the use of Jungle and just have Monit start the processes up.
Is it true puma has rolling restart feature? I couldn’t find any documentation on it.
Yea, rolling restart is a great feature for a ruby app server, take a look at https://github.com/puma/puma/blob/master/README.md#restart
Great article, thank you Tommy!
Since I needed to run several applications with this setup I created a gem to integrate the cap tasks: capistrano-puma (https://github.com/xijo/capistrano-puma). I hope it’s ok for you.
That’s great, i’ll edit to include a link to your project.
Perfect! =D Thanks
Tommy,
The path “/data/apps/” is the path of my local application or application server path of the amazon?
It’s the application server path.
Reblogged this on My Corner of the Web and commented:
Nginx and Puma
Wonderful, Thanks