Deploying Rails application with Nginx, Puma and Mina
In this tutorial, I am going to show you how to setup your server to run Rails application on Nginx with Puma server, and how to deploy the app with Mina tool.
- Installing Ruby
====
First, we need to install some dependencies for Ruby
sudo apt-get update
sudo apt-get install git-core curl zlib1g-dev build-essential libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev libcurl4-openssl-dev python-software-properties libmysqlclient-dev
Next, we are going to install Ruby using Ruby Version Manager (RVM). RVM allows us to quickly add/remove, upgrade/downgrade Ruby version without affecting other running applications on the server.
Note: The script for installing RVM is having issue with GPG, run the following command to download the key and import to your system first
curl -#LO https://rvm.io/mpapis.asc
gpg --import mpapis.asc
And then, start installing RVM as usual
sudo apt-get install libgdbm-dev libncurses5-dev automake libtool bison libffi-dev
curl -L https://get.rvm.io | bash -s stable
source ~/.rvm/scripts/rvm
echo "source ~/.rvm/scripts/rvm" >> ~/.bashrc
rvm install 2.1.2
rvm use 2.1.2 --default
Since Rails needs a JavaScript intepreter, we will need to install Node.js library as well
sudo apt-get install nodejs
We also need to install bundler
gem install bundler --no-ri --no-rdoc
- Setup directories & configure Mina
====
To make it easier to illustrate, I will use myapp
as my application name, replace it with your own application name if you want.
First of all, create a directory in your server called /var/www/myapp
, this is where the code is hosted. We also need to change the ownership of that directory to deploy
user (or any user who you are using to deploy)
sudo mkdir -p /var/www/myapp
sudo chown deploy:deploy /var/www/myapp
Mina
Mina (http://nadarei.co/mina/) is a great tool for deployment and it is much faster than Capistrano. Previously, Capistrano was my first choice, but from now on, I think Mina will be my default deployment tool :).
It is very easy to setup Mina, you need to install it as a gem (no need to put it in Gemfile)
gem install mina
Then run
mina init
to generate config/deploy.rb
file, which contains basic configuration of deployment.
Pay attention to some important lines in that file
set :user, 'deploy'
set :domain, 'myapp.com'
set :deploy_to, '/var/www/myapp'
set :app_path, lambda { "#{deploy_to}/#{current_path}" }
set :repository, 'git@github.com:example/example.git'
set :branch, 'master'
set :forward_agent, true
Next, run the following command at project location
mina setup
This will automatically log in to server and create two folders shared
and releases
inside /var/www/myapp
directory.
Be sure to edit the content of database.yml
and application.yml
config file on server. They are located at /var/www/myapp/shared/config/
folder. If you are wondering what application.yml
is used for, read this post: Gem Idol: Figaro
All the following steps assume that you have already updated the database.yml
and application.yml
with correct information. The database needs to be existed before continuting with later steps.
Lastly, start deployment process
mina deploy
If you get the error message Host key verification failed
, try SSH into server and run the following command and try deploying again
ssh -T git@github.com #assuming your code is on Github, replace it with your Git server
After running mina deploy
, there would be a new directory called current
created in /var/www/myapp
. This location will be used as the root directory when configuring nginx
-
Installing Nginx
====
I often install it via package managersudo apt-get install nginx
After installing Nginx, we need to remove the default site
sudo rm /etc/nginx/conf.d/sites-enabled/default
Create a host config file at /etc/nginx/sites-available/my_app.conf
upstream my_app {
server unix:///var/www/myapp/shared/tmp/sockets/puma.sock;
}
server {
listen 80;
server_name myapp.com; # change to match your URL
root /var/www/myapp/current/public; # I assume your app is located at this location
location / {
proxy_pass http://my_app; # match the name of upstream directive which is defined above
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location ~* ^/assets/ {
# Per RFC2616 - 1 year maximum expiry
expires 1y;
add_header Cache-Control public;
# Some browsers still send conditional-GET requests if there's a
# Last-Modified header or an ETag header even if they haven't
# reached the expiry date sent in the Expires header.
add_header Last-Modified "";
add_header ETag "";
break;
}
}
Pay attention to root
and server_name
, you need to set the correct path to public folder of your application for root
, and use the correct URL for server_name
And then, we need to enable this site by creating a symlink in /etc/nginx/sites-enabled
sudo ln -sf /etc/nginx/sites-available/my_app.conf /etc/nginx/sites-enabled/my_app.conf
Finally, restart Nginx server
sudo service nginx restart
- Puma
====
In order to run Puma on server, we are going to add two files: Puma configuration file and a shell script to restart Puma.
Create a new file at config/puma.rb
with the following content
#!/usr/bin/env puma
environment ENV['RAILS_ENV'] || 'production'
daemonize true
pidfile "/var/www/myapp/shared/tmp/pids/puma.pid"
stdout_redirect "/var/www/myapp/shared/tmp/log/stdout", "/var/www/myapp/shared/tmp/log/stderr"
threads 0, 16
bind "unix:///var/www/myapp/shared/tmp/sockets/puma.sock"
Replace the path if necessary.
Next, create a file at bin/puma.sh
with the below content
#! /bin/sh
PUMA_CONFIG_FILE=/var/www/myapp/current/config/puma.rb
PUMA_PID_FILE=/var/www/myapp/shared/tmp/pids/puma.pid
PUMA_SOCKET=/var/www/myapp/shared/tmp/sockets/puma.sock
# check if puma process is running
puma_is_running() {
if [ -S $PUMA_SOCKET ] ; then
if [ -e $PUMA_PID_FILE ] ; then
if cat $PUMA_PID_FILE | xargs pgrep -P > /dev/null ; then
return 0
else
echo "No puma process found"
fi
else
echo "No puma pid file found"
fi
else
echo "No puma socket found"
fi
return 1
}
case "$1" in
start)
echo "Starting puma..."
rm -f $PUMA_SOCKET
if [ -e $PUMA_CONFIG_FILE ] ; then
bundle exec puma -C $PUMA_CONFIG_FILE
else
bundle exec puma
fi
echo "done"
;;
stop)
echo "Stopping puma..."
kill -s SIGTERM `cat $PUMA_PID_FILE`
rm -f $PUMA_PID_FILE
rm -f $PUMA_SOCKET
echo "done"
;;
restart)
if puma_is_running ; then
echo "Hot-restarting puma..."
kill -s SIGUSR2 `cat $PUMA_PID_FILE`
echo "Doublechecking the process restart..."
sleep 5
if puma_is_running ; then
echo "done"
exit 0
else
echo "Puma restart failed :/"
fi
fi
echo "Trying cold reboot"
bin/puma.sh start
;;
*)
echo "Usage: script/puma.sh {start|stop|restart}" >&2
;;
esac
Finally, update Mina deploy.rb
to include a new step which will be in charge of restarting Puma after deploying
desc "Deploys the current version to the server."
task :deploy => :environment do
deploy do
# Put things that will set up an empty directory into a fully set-up
# instance of your project.
invoke :'git:clone'
invoke :'deploy:link_shared_paths'
invoke :'bundle:install'
invoke :'rails:db_migrate'
invoke :'rails:assets_precompile'
invoke :'deploy:cleanup'
to :launch do
invoke :'puma:restart'
end
end
end
namespace :puma do
desc "Start the application"
task :start do
queue 'echo "-----> Start Puma"'
queue "cd #{app_path} && RAILS_ENV=#{stage} && bin/puma.sh start", :pty => false
end
desc "Stop the application"
task :stop do
queue 'echo "-----> Stop Puma"'
queue "cd #{app_path} && RAILS_ENV=#{stage} && bin/puma.sh stop"
end
desc "Restart the application"
task :restart do
queue 'echo "-----> Restart Puma"'
queue "cd #{app_path} && RAILS_ENV=#{stage} && bin/puma.sh restart"
end
end
Important note:
You have to add execute permission to puma.sh
file to make sure it could be executed on the server
chmod +x bin/puma.sh
And be sure to check puma.sh
and puma.rb
into the remote repository before deploying.
After configuring all step above, we should be able to deploy the application by running the following command in the project folder
mina deploy
References
This tutorial contains material from several other blog posts, I recommend reading them to further enhance your knowledge on these things
- How to setup Rails app with Puma and Nginx
- Deploy Ruby on Rails on Ubuntu 14.04 Trusty Tahr
- Gist with the code for deploying Rails on Nginx + Puma using Mina
- How To Use Mina to Deploy a Ruby on Rails Application
That's all, if you have any issue when setting up the server with these steps, leave a comment and I will be ready to help