Deploying Rails application on AWS, using Nginx, Puma and Mina

Server Setup Following is the setup instruction for ubuntu 16.04 using ruby 2.4, Postgres 9.6 and rails 5.

Installing Ruby

Setting up 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 libffi-dev nodejs
Next step is to install ruby using RVM.
sudo apt-get install libgdbm-dev libncurses5-dev automake libtool bison libffi-dev
gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
curl -sSL https://get.rvm.io | bash -s stable
source ~/.rvm/scripts/rvm
rvm install 2.4.0
rvm use 2.4.0 --default
Last step is to install bundler.
gem install bundler

Installing Rails

Installing NodeJS, this lets you use Coffee script and the Asset Pipeline in Rails which combines and minifies your javascript to provide a faster production environment.
curl -sL https://deb.nodesource.com/setup_4.x | sudo -E bash -
sudo apt-get install -y nodejs
Next is to install rails.
gem install rails -v 5.0.1
You can verify the installation of rails version.
rails -v
# Rails 5.0.1

Installing PostgreSQL

sudo sh -c "echo 'deb http://apt.postgresql.org/pub/repos/apt/ xenial-pgdg main' > /etc/apt/sources.list.d/pgdg.list"
wget --quiet -O - http://apt.postgresql.org/pub/repos/apt/ACCC4CF8.asc | sudo apt-key add -
sudo apt-get update
sudo apt-get install postgresql-common
sudo apt-get install postgresql-9.5 libpq-dev
The postgres installation doesn’t setup a user for you, so you’ll need to follow these steps to create a user with permission to create databases.
sudo -u postgres createuser pankaj -s

Install Nginx

sudo apt-get update
sudo apt-get install nginx
Following are some basic commands to manage Nginx:
#Stop Nginx
sudo systemctl stop nginx
#Start Nginx
sudo systemctl start nginx
#Restart Nginx
sudo systemctl restart nginx

Nginx Configuration

First disable default site by removing the symlink.
sudo rm /etc/nginx/sites-enabled/default
Now create a new virtual host config file.
cd /etc/nginx/sites-available/
touch my_app.conf
edit the my_app.conf file:
upstream my_app {
  server unix:///var/run/my_app.sock; #path to puma socket file
}
server {
  listen 80;
  server_name my_app_url.com; # change to match your URL
  root /var/www/my_app/public; # I assume your app is located at that 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;
  }
}

Puma Configuration

Change config/puma.config file according to following example:
# Change to match your CPU core count
workers 2
# Min and Max threads per worker
threads 1, 6
app_dir = File.expand_path("../..", __FILE__)
shared_dir = "#{app_dir}/shared"
# Default to production
rails_env = ENV['RAILS_ENV'] || "production"
environment rails_env
# Set up socket location
bind "unix://#{shared_dir}/sockets/puma.sock"
# Logging
stdout_redirect "#{shared_dir}/log/puma.stdout.log", "#{shared_dir}/log/puma.stderr.log", true
# Set master PID and state locations
pidfile "#{shared_dir}/pids/puma.pid"
state_path "#{shared_dir}/pids/puma.state"
activate_control_app
on_worker_boot do
  require "active_record"
  ActiveRecord::Base.connection.disconnect! rescue ActiveRecord::ConnectionNotEstablished
  ActiveRecord::Base.establish_connection(YAML.load_file("#{app_dir}/config/database.yml")[rails_env])
end

Create puma upstart script

Let’s create an Upstart init script so we can easily start and stop Puma.
cd ~
wget https://raw.githubusercontent.com/puma/puma/master/tools/jungle/upstart/puma-manager.conf
wget https://raw.githubusercontent.com/puma/puma/master/tools/jungle/upstart/puma.conf
Now open the provided puma.conf file, so we can configure the Puma deployment user.
vi puma.conf
Look for the two lines that specify setuid and setgid, and replace “apps” with the name of your deployment user and group.
setuid ubuntu
setgid ubuntu
Now copy the scripts to the Upstart services directory.
sudo cp puma.conf puma-manager.conf /etc/init
The puma-manager.conf script references /etc/puma.conf for the applications that it should manage. Let’s create and edit that inventory file now.
sudo vi /etc/puma.conf
Each line in this file should be the path to an application that you want puma-manager to manage. Add the path to your application now. For example.
/path_to_application
Jungle upstart script provides following commands to manage puma app server:
#Start Puma
sudo start puma-manager
#Stop Puma
sudo stop puma-manager
#Restart Puma
sudo restart puma-manager

Install Mina

Add in your Gemfile.
gem 'mina'
and run bundle install to install mina.

Create deployment script

mina init
It will create config/deploy.rb, let’s edit it as following:
require 'mina/rails'
require 'mina/git'
require 'mina/rvm'
set :user, 'ubuntu' #deploy user
set :application_name, 'my app'
set :domain, 'myapp.com'
set :identity_file, 'myapp.pem' # ec2 instance key file
set :deploy_to, '/var/www/my_app' #path to app
set :app_path, lambda { "#{fetch(:deploy_to)}/#{fetch(:current_path)}" }
set :repository, '[email protected]:example/myapp.git' #Remote Repo Path
set :branch, 'master'
set :shared_paths, ['log', 'tmp']
set :shared_dirs, fetch(:shared_dirs, []).push('tmp')
task :environment do
  invoke :'rvm:use', '[email protected]'
end
task :setup do
  #create the folder structure
end
desc "Deploys the current version to the server."
task :deploy do
  deploy do
    invoke :'git:clone'
    invoke :'deploy:link_shared_paths'
    invoke :'bundle:install'
    invoke :'rails:db_migrate'
    invoke :'rails:assets_precompile'
    invoke :'deploy:cleanup'
    on :launch do
      invoke :'puma:restart'
    end
  end
end
namespace :puma do
  desc "Start the application"
  task :start do
    command 'echo "-----> Start Puma"'
    command "sudo start puma-manager", :pty => false
  end
  desc "Stop the application"
  task :stop do
    command 'echo "-----> Stop Puma"'
    command "sudo stop puma-manager"
  end
  desc "Restart the application"
  task :restart do
    command 'echo "-----> Restart Puma"'
    command "sudo restart puma-manager"
  end
end
Now to setup directory structure run:
mina setup --verbose
To deploy the application run:
mina deploy --trace
  REFERENCES: https://gorails.com/setup/ubuntu/16.04 https://github.com/mina-deploy/mina https://github.com/puma/puma  ]]>