Run multiple Sinatra apps on a single server (CentOS 7)
There’s a considerable lack of information about rack apps deployment other than Ruby on Rails, so I made this small tutorial for deploying Sinatra apps to CentOS 7 servers.
I’ll use a Digital Ocean droplet as an example but should work anywhere else. These are some of the technologies we’ll use:
- CentOS 7 (Operative System)
- Nginx (Front Facing Web Server)
- Unicorn (App Server)
- Ruby 2.2.2 (Programming Language)
- Sinatra (DSL for Rack apps)
Server Configuration
This is a very basic server configuration, there are multiple steps to secure our server but we won’t talk about this on this post.
Log In
Conect to your server, log as the root
user using the following command (substitute the highlighted word with your server’s public IP address):
ssh root@SERVER_IP_ADDRESS
Complete the login process by accepting the warning about host authenticity if it appears, then providing your root authentication (password or private key).
Create user
This example creates a new user called “pggalaviz”, but you should replace it with a user name that you like:
adduser pggalaviz
Next, assign a password to the new user (again, substitute “pggalaviz” with the user that you just created):
passwd pggalaviz
Enter a strong password, and repeat it again to verify it. We have a new user account with regular account privileges. However, we need to do administrative tasks. As root, run this command to add your new user to the wheel group (substitute the highlighted word with your new user):
gpasswd -a pggalaviz wheel
Now your user can run commands with super user privileges!
Public Key Authentication
If you don’t have a public key already, generate one by running this command at the terminal of your local machine (your computer not the server)!
ssh-keygen
Press ‘return key’ to accept (don’t modify the path) and add a password to your keys (this last step is optional). If you already had or you just created your keys run this command on your local machine to print your public key (id_rsa.pub):
cat ~/.ssh/id_rsa.pub
it should print something like this:
ssh-rsa AAAAB3N.......zaC1yc2 localuser@machine.local
Select it all and copy it to the clipboard. On the server as the root user enter this command to switch to the user we created at the begining of this tutorial:
su - pggalaviz
Now we’re going to create a folder named .ssh and change its permissions for security reasons:
mkdir .ssh
chmod 700 .ssh
Create a new file by running:
vi .ssh/authorized_keys
Enter ‘insert’ mode by pressing i
and paste your previoulsy copied ssh key. press ESC and :x
to save changes.
Change the file permissions:
chmod 600 .ssh/authorized_keys
and return to the root user by running:
exit
Now, you’ll be able to login to your server by running something like
ssh pggalaviz@SERVER_IP_ADDRESS
Restrict Root Login
Log in to your server using your user (not root) and open the SSH configuration file by running:
vi /etc/ssh/sshd_config
Probably you’ll need to add sudo before the command.
Look for a line that looks like: #PermitRootLogin yes
.
Enter insert mode by pressing i
and edit the line so it looks like PermitRootLogin no
Disabling remote root login is highly recommended on every server!
Press :x
and enter to save changes and then run the following command to reload SSH.
sudo systemctl reload sshd.service
Install Dependencies
Install EPEL and update
First install the EPEL package:
sudo yum install epel-release
Then you should confirm that all packages are updated by running:
sudo yum update
Install Packages
Install all the packages needed such as gcc, make, git, binutils, etc.
sudo yum install -y git-core zlib zlib-devel gcc-c++ patch readline readline-devel libyaml-devel libffi-devel openssl-devel make bzip2 autoconf automake libtool bison curl sqlite-devel curl-devel
Setting Up a Ruby Environment
We’ll use ‘rbenv’ to install and manage Ruby versions, just run these commands:
cd
git clone git://github.com/sstephenson/rbenv.git .rbenv
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
exec $SHELL
git clone git://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
echo 'export PATH="$HOME/.rbenv/plugins/ruby-build/bin:$PATH"' >> ~/.bash_profile
exec $SHELL
you can check installation by running rbenv
.
If nothing appears then logout from the server, log in again using ssh and run rbenv
, everything should work now.
Now we’ll install Ruby:
rbenv install -v 2.2.2
This will install Ruby version 2.2.2, this can take some minutes. After install let’s set the default version our shell will use by running:
rbenv global 2.2.2
Finally let’s verify ruby was installed properly with this command:
ruby -v
After ruby is installed run this command to install gems without documentation and save yourself some time and disk usage:
echo "gem: --no-document" > ~/.gemrc
Now lets install some basic gems:
gem install bundler rack sinatra unicorn
Whenever you install a new version of Ruby or a gem that provides commands, you should run the rehash sub-command. This will install shims for all Ruby executables known to rbenv, which will allow you to use the executables:
rbenv rehash
Create a simple Sinatra App
Just for this tutorial lets create a simple sinatra app.
cd
mkdir sample_app
cd sample_app
We’ll use Sinatra’s modular style, start by creating the following archives and folders inside our sample_app folder:
assets
|_ css
|_app.scss
|_ js
|_images
log
public
tmp
|_pids
|_sockets
views
|_ layouts
|_ home.erb
app.rb
config.ru
Gemfile
unicorn.rb
Inside our Gemfile lets add:
#=> Gemfile
source "https://rubygems.org"
gem 'sinatra'
gem 'sass'
gem 'unicorn'
Inside the app.rb file lets add:
#=> app.rb
ENV['RACK_ENV'] ||= 'development'
$: << File.expand_path('../', __FILE__)
require 'sinatra/base'
require 'sass'
module SampleApp
class App < Sinatra::Base
#****************
#Configuration
#****************
set :root, File.dirname(__FILE__)
configure do
enable :sessions
end
#****************
#Assets Routes
#****************
get '/css/*.css' do
content_type 'text/css', :charset => 'utf-8'
filename = params[:splat].first
scss filename.to_sym, :views => "#{settings.root}/assets/css", :style => :compressed
end
#****************
#Main Routes
#****************
get '/' do
erb :home
end
end
end
Inside the config.ru lets add:
#=> config.ru
require 'rubygems'
require 'bundler'
Bundler.require :default, ENV['RACK_ENV'].to_sym
require File.expand_path '../app.rb', __FILE__
run SampleApp::App
Inside the views/home.erb lets add:
<html>
<head>
<meta charset="UTF-8">
<title>Sample App</title>
<link rel="stylesheet" type="text/css" href="/css/app.css">
</head>
<body>
<h1>Pedro G. Galaviz</h1>
<p>If you can see me, it's working!</p>
</body>
</html>
Finally on app’s directory run:
bundle install
Configure Unicorn
Now we need to configure Unicorn to serve our app.
cd
cd sample_app
vi unicorn.rb
And copy this inside the file:
root = "/home/pggalaviz/sample_app"
worker_processes 2
working_directory root
timeout 30
pid "#{root}/tmp/pids/sample_app.pid"
stderr_path "#{root}/log/unicorn.log"
stdout_path "#{root}/log/unicorn.log"
listen "#{root}/tmp/sockets/sample_app.sock"
Create Unicorn Init Script
Now we’ll create an init script so we can manage our unicorn server.
sudo vi /etc/init.d/unicorn_sample_app
And inside this file we’ll add:
#!/bin/sh
### BEGIN INIT INFO
# Provides: unicorn
# Required-Start: $all
# Required-Stop: $all
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: starts the unicorn app server
# Description: starts unicorn using start-stop-daemon
### END INIT INFO
set -e
USAGE="Usage: $0 "
# app settings
USER="pggalaviz"
APP_NAME="sample_app"
APP_ROOT="/home/$USER/$APP_NAME"
ENV="production"
# environment settings
PATH="/home/$USER/.rbenv/shims:/home/$USER/.rbenv/bin:$PATH"
CMD="cd $APP_ROOT && bundle exec unicorn -c unicorn.rb -E $ENV -D"
PID="$APP_ROOT/tmp/pids/$APP_NAME.pid"
OLD_PID="$PID.oldbin"
# make sure the app exists
cd $APP_ROOT || exit 1
sig () {
test -s "$PID" && kill -$1 `cat $PID`
}
oldsig () {
test -s $OLD_PID && kill -$1 `cat $OLD_PID`
}
case $1 in
start)
sig 0 && echo >&2 "Already running" && exit 0
echo "Starting $APP_NAME"
su - $USER -c "$CMD"
;;
stop)
echo "Stopping $APP_NAME"
sig QUIT && exit 0
echo >&2 "Not running"
;;
force-stop)
echo "Force stopping $APP_NAME"
sig TERM && exit 0
echo >&2 "Not running"
;;
restart|reload|upgrade)
sig USR2 && echo "reloaded $APP_NAME" && exit 0
echo >&2 "Couldn't reload, starting '$CMD' instead"
$CMD
;;
rotate)
sig USR1 && echo rotated logs OK && exit 0
echo >&2 "Couldn't rotate logs" && exit 1
;;
*)
echo >&2 $USAGE
exit 1
;;
esac
Check that you change # app settings
to your app and user data.
Save and exit the file. Now to enable unicorn to start on boot lets run:
sudo chmod 755 /etc/init.d/unicorn_sample_app
sudo chkconfig --levels 235 unicorn_sample_app on
Lets start our Unicorn server now:
sudo service unicorn_sample_app start
Configure Nginx
Install Nginx
First we need to install Nginx to our server. Just run:
sudo yum install nginx
next, we’ll enable it to start on server boot:
sudo chkconfig --levels 235 nginx on
Configuration
First we’ll open the nginx configuration file by running:
sudo vi /etc/nginx/nginx.conf
Then change its content to look like the one below.
user pggalaviz;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
gzip on;
index index.html index.htm;
include /etc/nginx/conf.d/*.conf;
}
Check you changed the user to yours. Now let’s create our server file:
sudo vi /etc/nginx/conf.d/sample_app.conf
and lets add this content:
upstream sample_app_x {
server unix:/home/pggalaviz/sample_app/tmp/sockets/sample_app.sock fail_timeout=0;
}
server {
listen 80;
server_name localhost sample_app.com;
root /home/pggalaviz/sample_app/public;
try_files $uri/index.html $uri @app;
location @app {
proxy_pass http://sample_app_x;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
}
error_page 500 502 503 504 /500.html;
client_max_body_size 4G;
keepalive_timeout 10;
}
Change to match your user and app’s data. Save and exit. Now let’s start our nginx server by running:
sudo service nginx start
If everything worked fine you should be able to visit your server IP address or your domain (if you already pointed it to the server) and see our home page.
By following this steps you can deploy any number of apps in the same server, just limited by your server’s resources.
Updating & Adding apps
Let’s assume you’re using GIT as your version control system and your Sinatra app code is living there (Any app).
You can log in to your server and clone the git repo to the folder where your apps will live.
For each app you’ll need to create a “unicorn.rb” file inside the app’s folder then a “Unicorn init script” and an “Nginx configuration file” on the server as we previously did.
you can start any unicorn process by typing:
sudo service unicorn_sample_app start
If you are updating your app first pull the repo code from git to your app’s folder and then type:
sudo service unicorn_sample_app restart
sudo service nginx restart
And your app should be updated accordingly.