Learning How To Deploy Rails

Johnson Zhan
Aug 19, 2018 · 10 min read

If you are studying in Rails deployment, you must heard about the tool like Nginx, Passenger, Capistrano! In this article, I will give you a brief introduction of these tools and the common features of them!

How is the way deploy like? Maybe is like the following picture

Remote server is like GCP, AWS or DigitalOcean. Git repositories is a place to put your code, e.g., GitHub, GitLab and Bitbucket. The deploy flow is to make remote server get the code from git repositories and build the project to provide services. We need to install Passenger and Nginx on our remote server, using Capistrano to supports the scripting and execution of arbitrary tasks.


Category

  • Nginx & Passenger
  • Why Nginx with Passenger ?
  • Advantage of Passenger ?
  • Commond of Passenger
  • Nginx configuration file
  • Setting your Nginx
  • Capistrano
  • Capistrano features
  • Capistrano structure
  • Capistrano configuration file
  • Commond of Capistrano
  • Conclusion

Nginx & Passenger

Nginx is a Web Server. It manage the http request and return http response. Nginx dosen’t know how to read ruby, we used to set up Nginx with Passenger which is an App Server to manage the number of web application processes.

In the following article I will use these three terms
Web Server is Nginx
App Server is Passenger
Web Application is Rails App

Why Nginx with Passenger ?

This is one of the part hard to understand. In my original imagination, the structure is web application with some service or software which handle the job my web application dosen’t do. Why we need both Passenger and Nginx to complete this work?

Nginx and Apache are web server which are good at handling http request and static file. Generally, we put the web application or its application server behind a real web server in a reverse proxy setup. This structure make our system more secure and robust.

Additionally, web server also take care with performance issue. Such as slow client issue, these clients may send http request slowly and response http slowly. Some HTTP server may spend so much time waiting I/O and spend very littile time doing actual work. To solve this problem without reinvent their wheel, the better way is to let web server handle this issue. That is why we need both Nginx and Passenger to build our system!

Advantage of Passenger ?

Passenger takes care of application spawning, which means we can start server application process depends on situation. Passenger also play a role between web application and web server. It communicate with Nginx and speak with Rails app under rake protocol. On the structure side, Passenger is stable and flexible. Following is Phusion Passenger architecture overview

source:www.phusionpassenger.com

Passenger consists of multiple components, including Phusion Passenger module which compile with Nginx. These passenger components run on serveral process. Watchdog one of this components which monitoring other components and restart them preventing passenger crash.

Due to there are several processes running in this system, they need to communicate with each other by some files. We stored these files in a directory call instance directory. In nginx.conf we can set up which folder is instance directory. The Passenger commond such as passenger-status also use instance directory to get the information.

I used to be unable to run Passenger, and I don’t know the reason.
It turns out that the directory instance directory is missing!

More information about this architecture.

Commond of Passenger

To use the commond of Passenger, we need to place Passenger’s bin directory under our path. We can find Passenger’s execution file under this bin directory.

passenger  passenger-config  passenger-install-apache2-module  passenger-install-nginx-module  passenger-memory-stats  passenger-status

These execution files is also the commond available!

I usually use these two commonds

# checkout passenger status
passenger-status

# restart Rails app by Passenger
passenger-config restart-app

Where to set up Passenger configuration file? Remembering there is a Phusion Passenger module which compile with Nginx. By Phusion Passenger module, we can set some Passenger config on Nginx configuration file.

Nginx configuration file

We will have nginx.conf configuration file after you installed Nginx. We can directly modify this file or set the config like include site-enabled/*.conf; to include other configuration file. In the meantime, we need to create site-enabled directory under the path where you have nginx.conf file.

If you commond out part of settings in nginx.conf, this file like so:

# Tips0
worker_processes 1;

events {
worker_connections 1024;
}

http {
passenger_root /usr/local/passenger/passenger-5.3.3;
passenger_ruby /usr/local/ruby-2.4.3/bin/ruby;
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;

server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}

# We add this line manually
include site-enabled/*.conf;
}

In nginx.conf you will find http { ... } block and there are server{...} blocks inside it. Our include site-enabled/*.conf; is inside http { ... } block and beside server{...} block. We will implement several server{...} block in our configuration file. You can get more information about this structure from official documents.

Tips 0

Set the number of Nginx worker_processes. Nginx has one master process and several worker processes. Master process can read the configuration and maintain worker processes. Worker process is who really handel http request!

Setting your Nginx

We can set our configuration file by the following commond

$ mkdir site-enabled
$ cd site-enabled

# usually we will use domain name as the name of configuration file
$ vim xxx.conf

Setting xxx.conf like this (# is commond out symbol)

# set instance directory of Passenger
passenger_instance_registry_dir /var/run/passenger-instreg;

# first server block
server {

# listen on port 80 ( http request )
listen 80;

# tips 1 setting server name
server_name dodeploy.tk;

# tips 2, redirect http request to second server block
return 301 https://dodeploy.tk$request_uri;
}

# second server block
server {

# listen on port 443 ( https request )
listen 443 ssl;

server_name dodeploy.tk;

# SSL certification setting
ssl on;
ssl_certificate /etc/letsencrypt/live/dodeploy.tk/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/dodeploy.tk/privkey.pem;

# tips 3
location ~ /.well-known {
allow all;
root /opt/nginx/html;
}

# set the directory of static file
root /home/deploy/do_deploy.com/current/public;

# enable Passenger
passenger_enabled on;
}

Tips 1

We can set several domain name point to out server. First of all, apply serveral domain name to the server IP. Then we can use several server blocks to listen on each http request with different domain name.

Tips 2

In this example, we will redirect http request to https request. To implement this feature we need a least two server block. One listen on port 80 whcih is http request. After receive the http request it will redirect it to https request which will received by the other server block listening on port 443.

Tips 3

At the second server block we set the SSL certificate. The purpose of location block is to let the request with ~/.well-known path return the files in opt/nginx/html directory. This is the way to pass Let's Encript.

After completing the above settings, use the following commond to check the nginx config file.

sudo /opt/nginx/sbin/nginx -t

If all the things on the way, use following commond to restart nginx.

sudo systemctl restart nginx

Capistrano

Capistrano features

Capistrano is a remote server automation tool. Without Capistrano we need to do a lot effort to complete the deploy flow. If wee need deploy mulitple server or the deploy task is more complicated, we will definitely want to ues Capistrano.

Capistrano support multiple stages. The stage means environment run web application such as production or staging. We only need to define the deploy configuration one time then we can use these settings on every stage.

Define your deployment once, and then easily parameterize it for multiple stages (environments), e.g. qa, staging, and production. No copy-and-paste necessary: you only need to specify what is different for each stage, like IP addresses.

Your application may need many different types of servers: a database server, an app server, two web servers, and a job queue work server, for example. Capistrano lets you tag each server with one or more roles, so you can control where to execute what tasks.

Capistrano structure

Following is Capistrano structure

├── current -> /home/deploy/do_deploy.com/releases/20150120114500/
├── releases
│ ├── 20150080072500
│ ├── 20150090083000
│ ├── 20150100093500
│ ├── 20150110104000
│ └── 20150120114500
├── repo
│ └── <VCS related data>
├── revisions.log
└── shared
└── <linked_files and linked_dirs>

Capistrano will create a project directory after every deployment. The latest code is in latest prodject directory in relaeses folder. The source of current folder is also symlink from the latest prodject directory.

There are some directory or file we want to share between each releases such as log directory in 20150080072500 and 20150090083000 release. There are also some sensitive file like database.yml or settings.yml whcih isn't in version control we will put it in shared folder. How to put them into shared folder? It depends on the setting in Capistrano.

Capistrano configuration file

The configuration file of Capistrano

├── Capfile
├── config
│ ├── deploy
│ │ ├── production.rb
│ │ └── staging.rb
│ └── deploy.rb
└── lib
└── capistrano
└── tasks

In this part I will introduce these files Capfile、deploy.rb、prdouction.rb

You can define your Capistrano by add the setting on Capfile. The setting include which commond you can use and what happen after you execute the commond.

A new Capfile after removing the code which is commented out.

# Load DSL and set up stages
require "capistrano/setup"

# Include default deployment tasks
require "capistrano/deploy"

require "capistrano/scm/git"
install_plugin Capistrano::SCM::Git

# require "capistrano/bundler"
# require "capistrano/rails/assets"
# require "capistrano/rails/migrations"
# require "capistrano/passenger"

# Load custom tasks from `lib/capistrano/tasks` if you have any defined
Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r }

To deploy Rails App and smooth the deploy process, I will add following lines in Capfile.

require "capistrano/bundler"
require "capistrano/rails/assets"
require "capistrano/rails/migrations"
require "capistrano/passenger"
require 'capistrano/upload-config'

Above settings is for Rails deployment. What is the actual functionality of these settings? We can find the difference when we run cap production deploy commond. More information will introduced later.


Configuration in Nginx can be either global or specific to your stage. The stage is the environment run the web application such as staging or production. The configuration for all the stage is global such as deploy.rb. The file under config/deploy/ directory is the specific configuration file.

config/deploy.rb

global setting

lock "~> 3.10.1"

# set project name
set :application, "do_deploy"

# set the url link of git repository
set :repo_url, "git@github.com:shes50103/do_deploy.git"

# the files in shared folder and not in version control
append :linked_files, "config/database.yml", "config/secrets.yml", "config/settings.yml"

# the directories in shared folder and shared by each release
append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "public/system"

config/deploy/production.rb

stage specific setting

# set the deployment environment of Rails
set :rails_env, :production

# set the branch to deploy
set :branch, 'master'

# set the path to deploy project
set :deploy_to, '/home/deploy/do_deploy.com'

# find information from following explanation
role :web, %w{deploy@178.128.xxx.xxx}
role :db, %w{deploy@178.128.xxx.xxx}

Although it is not a good idea, we can set all the configuration in config/deploy/production.rb file. It still work but the problem is if you want to set another stage such as staging. You need to set all your configuration again.

During deploy flow, we will ssh from localhost to remote server. To use ssh we need remote server IP and the account. I will use the account deploy to ssh to remote server which means there are also a user named deploy in remote server. At the same time, we need to have ssh public key in deploy's ~/.ssh/authorized_keys file.

Besides, when I logging as deploy user I still need to ssh from remote server to repository. I also need to set the deploy ssh public key to git repository. Remember server roles which is one of the feature of Capistrano. The role :web and role :db is the server roles we have mentioned.

Opps!? Do we need to set more configuration? There are a lot of default settings in Capistrano like

# the maximum number of directories in release folder is five
set :keep_releases, 5

# default version control system is Git
set :scm, :git

Cause there are a lot of default values we can just set the configuration we want to modify. More information on official document.

Commond of Capistrano

After set the configuration. We need to type commond to do something. How do I know which commond I can use? You can checkout from the following commond.

bundle exec cap -T

You can list the commond which is available in the Capistrano. The commonds Capistrano provide depends on what you require in Capfile. Try to comment out part of require code in Capfile. You will find out the number of commonds you can use is decreased.


bundle exec cap production config:pull
bundle exec cap production config:push

Before use these commonds, you need to install gem 'capistrano-upload-config' and add require 'capistrano/upload-config' in Capfile. These two commonds help you push and pull the linked_files in remote server.


bundle exec cap production deploy:check

This commond help us check the project directory, linked_dirs and linked_files. If there are something we don't have, Capistrano help us to create the corresponding directory. Before a new project deployment, we will use this commond to create shared directory. Then use cap production config:push to push the config file.


The next one is the commond of deployment!

bundle exec cap production deploy

What can this commond do? It depends on the content of Capfile. If there are only the following two lines in Capfile

require "capistrano/setup"
require "capistrano/deploy"

It will execute following task when you run deploy commond

git:wrapper
git:check
deploy:check:directories
deploy:check:linked_dirs
deploy:check:make_linked_dirs
git:clone
git:update
git:create_release
deploy:set_current_revision
deploy:symlink:linked_files
deploy:symlink:linked_dirs
deploy:symlink:release
deploy:cleanup
deploy:log_revision

To deploy Rails projecy we need to add more settings in Capfile

require "capistrano/bundler"
require "capistrano/rails/assets"
require "capistrano/rails/migrations"

Then you can find more step when you run deploy commond

bundler:install
deploy:assets:precompile
deploy:assets:backup_manifest
deploy:migrate
deploy:migrating

These five tasks will run between deploy:symlink:linked_dirs and deploy:symlink:release

There are two common mistakes when I use Capistrano. First is some of tasks will only execute under specific role. Another mistakes is during some installation process we need to specific the correct path or the error will happen. More information about deploy flow click here

Conclusion

Although I already work through the hole process of deployment. I still not really know how is the deployment work. Sometime I find the way to solve problem but I don’t know the principle behind my way. This article is a chance for me to study more about deployment and master my skill.

    Johnson Zhan

    Written by

    Rails developer

    Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
    Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
    Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade