Tips and Resources For Setting Up Website From Ground Up


This is a summary of the whole procedure of How to build your own website using Rails Framework. It sounds really simple in the Rails Guide, but if really want to go over the whole procedure, there are steps beyond writing a small App that runs on your own computer. It is kind of a summary that reminds me things I need to care about when running a serious App in production environment, so I think it suits people who just learn how to build Rails App while want to know more about how to actually run it in production environment. This Topic covers all this contents below:

  1. Simple Rails App & Github
  2. Domain, DNS & Server
  3. Deployment
  4. Web Server & App Server

In the future there might have:

  • Database
  • Cache & Queue
  • Security

But for now I’m only focusing on getting through the most basic steps.


1.Simple Rails App & Github

If you already familiar with Rails and Github, just ignore this first step.

Build a Toy Rails App is probably the most easy part of this article, just follow the detailed Getting Started with Rails article here. To follow up, please go over it and I’m assuming you now have a code repository called blog on your own computer, and with command line rails s you can run it and view it in browser with address localhost:3000.

If you consider Rails too fancy for you and you just care about building static website (e.g.: for blog), use Jekyll is also a nice choice to get started, with this guide you can setup a toy blog repository in seconds, and view in with localhost:4000.

To set up production environment, you also need Version Control, nowadays most web developers use Git, you can use git with Github, or you can set your own Gitlab Web App to manage your code, or else you can just simply use git on server solely with command line, its your choice to use which method to apply. Though this article mainly focus on using Git with Github. There is also a very nice written procedure on Github here, after following these guidelines you should have a github repository called blog now.


2.Domain, DNS & Server

Many companies provides their own whole package of App management service, like Amazon AWS, Heroku etc, and to be more advanced, people also invent a container called Docker. They are pretty good if you want to develop/copy a prototype really fast. But to some extent, for beginners, they are too much and might freaks you out with their loads of services. So let’s separate them to several basic pieces first.

Domain

To make your project online, you need a domain, which is simply an address you have on the Internet. You can easily get a third level domain for free if you just want to publish a blog(e.g.: myblog.tumblr.com), but to get your own second level domain, you probably need to rent one. (Let me know if you can find a free one. :) ) Personally I use domain providers like GoDaddy and Gandi, and I prefer Gandi more because their Interface is cleaner. To get a domain, simply go to one of this providers and rent one name you like, it won’t be too expensive for the first year. After the payment you should get a domain name like foobar.com

Server

Most developers don’t use physical servers now, I suggest renting a simple virtual host, it is convenient because guys in charge of the real servers will help you fix security issues and upgrade your hardware without you even notice. Then all you need to care about is on the software side, besides, real servers are really expensive and too fancy to play with. I personally use Digital Ocean, they use SSD hard drives and the price of minimum setting is cheap. Another popular choice is Linode, I think they have a longer history and some little birds of mine said that the setting of Linode server is more comprehensive. After the payment and very easy settings (If you use Digital Ocean also), you should have a Server System installed, let’s assume it is the latest stable version of Ubuntu, and in order to make your App work, you also need set up the environment for your App to run, with sudo permission it shouldn’t be hard. And the most important thing is that, now you get your own server IP address like 123.132.213.231

DNS

DNS, according to Wikipedia, is short for Domain Name System, but to understand it in the most superficial way, it is the thing that connect your domain name and your IP address.

There are two awesome and practical articles that can lead you through the procedure of making this connection.

  1. How to Point to DigitalOcean Nameservers From Common Domain Registrars
  2. How To Set Up a Host Name with DigitalOcean

As you can observe, there are lots of good articles about server configuration on DigitalOcean, another reason I use it. After this step, you should able to ping your server with the domain name like the second article said.


3.Deployment

Most of times I use Capistrano, the thing about Capistrano is that there is some significant difference between Capistrano 2 and Capistrano 3, to keep up with the pace, I’ll look into details of Capistrano 3 here. FYI, there is also a deploy tool called Mina that is getting popular.

After install Capistrano via the Official Guide, there are three files you need to change. I’ve detailed comment the configuration usage in the code below.

/Capfile

# Load DSL and Setup Up Stages
require 'capistrano/setup'

# Includes default deployment tasks
require 'capistrano/deploy'

# Includes tasks from other gems included in your Gemfile
#
# For documentation on these, see for example:
#
#   https://github.com/capistrano/rvm
#   https://github.com/capistrano/rbenv
#   https://github.com/capistrano/chruby
#   https://github.com/capistrano/bundler
#   https://github.com/capistrano/rails
#
# require 'capistrano/rvm'
# require 'capistrano/rbenv'
# require 'capistrano/chruby'

# Add bundler support
require 'capistrano/bundler'
# Add assets precompile support
require 'capistrano/rails/assets'
# Add database migration support
require 'capistrano/rails/migrations'
# Add unicorn support
require 'capistrano3/unicorn'

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

/deploy.rb

# config valid only for Capistrano 3.4
lock '3.4.0'

# set the directory the code deployed to on server
# # Default deploy_to directory is /var/www/my_app
set :deploy_to, '/your_project'

# set your user name on server
set :user, "username"

# set the application name
set :application, 'app_name'

# set the repository address
set :repo_url, 'git@github.com:app_name.git'

# set the deployed branch
# Default branch is :master
# ask :branch, proc { `git rev-parse --abbrev-ref HEAD`.chomp }.call
set :branch, "your_branch"

# files we want sym linking to specific entries in shared.
# Default value for :linked_files is []
set :linked_files, %w{config/database.yml config/secrets.yml}

# dirs we want symlinking to shared
# Default value for linked_dirs is []
set :linked_dirs, %w{log tmp/pids tmp/cache tmp/sockets vendor/bundle public/system lib/data}

# Set application environment
set :rails_env, "production"

# Set deploy method
set :deploy_via, :copy

#########################################
# Set this configurations if you don't have sudo permission

# Deploy without using sudo if you are not sudo
set :use_sudo, false

# set tmp dir to your home directory(when you have no sudo permission)
set :tmp_dir, "/home/users/your_username/tmp"

# Default value for default_env is {}
# If you installed ruby locally(in your home directory, you need to specify its location to Capistrano)
set :default_env, { path: "/home/users/your_username/.rvm/gems/ruby-2.1.5/wrappers:$PATH" }

#########################################
# These are default settings that you probably don't need to change

# set software configuration management tool
# Default value for :scm is :git
# set :scm, :git

# Deploy console log format setting
# Default value for :format is :pretty
# set :format, :pretty

# Default value for :log_level is :debug
# set :log_level, :debug

# indicate use of sudo without password
# Default value for :pty is false
# set :pty, false

# Set the number of releases you want to keep
# Default value for keep_releases is 5
# set :keep_releases, 5

# Unicorn settings(We'll talk about it soon, just showcase the typical usage here)
set :unicorn_pid, -> { "#{shared_path}/tmp/pids/unicorn.pid" }
set :unicorn_config_path, "config/unicorn.rb"
set :unicorn_rack_env, 'deployment' # "development", "deployment", or "none"

namespace :deploy do

  desc 'Restart application'
  task :restart do
    on roles(:app), in: :sequence, wait: 5 do
      invoke 'unicorn:legacy_restart'
    end
  end

  after :publishing, 'deploy:restart'
  after :finishing,  'deploy:cleanup'
end

# After deploy cleanup, clean up old version dependencies.
namespace :bundle do
  task :clean do
    on roles(:web) do
      within release_path do
        with rails_env: fetch(:rails_env) do
          execute :bundle, :clean
        end
      end
    end
  end
end

after "deploy:cleanup", "bundle:clean"

/config/deploy/production.rb

# set deploy stage to production
set :stage, :production

# add your domain name and username on server
server 'mydomain.com', roles: %w{web app db}, user: 'username'

Then you’re all set on production deploy configuration.

4.Web Server & App Server

The easiest way is to use Passenger, the typical way is to use Nginx+Unicorn, besides Passenger and Unicorn, Puma is also a popular app server choice. Here I would describe how to set up Nginx+Unicorn on server.

First one need to install Nginx on server and unicorn gem, notice here that unicorn is a gem that should be part of the dependency of your Rails App, while Nginx is a popular web server that can work with lots of frameworks beyond Rails. After installation, there are three files that is important for configuration, I’ve commented the important part in the code below

Some of comments originally from other people’s scripts, the references are listed below. Thanks if any of you see this. :)

References:

How to Configure Nginx

Setting Up Your Production Server With Nginx and Unicorn

Capistrano3 を使って Rails4 + unicorn + nginx + rbenv にデプロイする

unicorn.rb

# Sample verbose configuration file for Unicorn (not Rack)
#
# This configuration file documents many features of Unicorn
# that may not be needed for some applications. See
# http://unicorn.bogomips.org/examples/unicorn.conf.minimal.rb
# for a much simpler configuration file.
#
# See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete
# documentation.

# app path in production environment
app_path = File.dirname(File.dirname(Dir.pwd))

# Use at least one worker per core if you're on a dedicated server,
# more will usually help for _short_ waits on databases/caches.
# But to be honest, for a small app 2 process is enough.
worker_processes (ENV['RAILS_ENV'] == 'production' ? 2 : 1)

# Since Unicorn is never exposed to outside clients, it does not need to
# run on the standard HTTP port (80), there is no reason to start Unicorn
# as root unless it's from system init scripts.
# If running the master process as root and the workers as an unprivileged
# user, do this to switch euid/egid in the workers (also chowns logs):
# user "unprivileged_user", "unprivileged_group"

# Help ensure your application will always spawn in the symlinked
# "current" directory that Capistrano sets up.
# working_directory "/path/to/app/current"
# available in 0.94.0+
working_directory "#{app_path}/current"

# listen on both a Unix domain socket and a TCP port,
# we use a shorter backlog for quicker failover when busy
listen app_path + '/shared/tmp/sockets/unicorn.sock', backlog: 64

# For development, you may want to listen on port 3000 so that
# you can make sure your unicorn.rb file is soundly configured.
listen(3000, backlog: 64) if ENV['RAILS_ENV'] == 'development'

# default timeout is 60 seconds
timeout 60

# Set the location of the unicorn pid file. This should match what we put in the
# unicorn init script later.
pid app_path + '/shared/tmp/pids/unicorn.pid'

# You should define your stderr and stdout here. If you don't, stderr defaults
# to /dev/null and you'll lose any error logging when in daemon mode.
stderr_path app_path + '/shared/log/unicorn.stderr.log'
stdout_path app_path + '/shared/log/unicorn.stdout.log'

# combine Ruby 2.0.0 dev or REE with "preload_app true" for memory savings
# http://rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
preload_app true
GC.respond_to?(:copy_on_write_friendly=) and
  GC.copy_on_write_friendly = true

# Enable this flag to have unicorn test client connections by writing the
# beginning of the HTTP headers before calling the application.  This
# prevents calling the application for connections that have disconnected
# while queued.  This is only guaranteed to detect clients on the same
# host unicorn runs on, and unlikely to detect disconnects even on a
# fast LAN.
check_client_connection false

# local variable to guard against running a hook multiple times
run_once = true

before_fork do |server, worker|
  # the following is highly recomended for Rails + "preload_app true"
  # as there's no need for the master process to hold a connection
  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.connection.disconnect!

  # Occasionally, it may be necessary to run non-idempotent code in the
  # master before forking.  Keep in mind the above disconnect! example
  # is idempotent and does not need a guard.
  if run_once
    # do_something_once_here ...
    run_once = false # prevent from firing again
  end

  # The following is only recommended for memory/DB-constrained
  # installations.  It is not needed if your system can house
  # twice as many worker_processes as you have configured.
  #
  # This allows a new master process to incrementally
  # phase out the old master process with SIGTTOU to avoid a
  # thundering herd (especially in the "preload_app false" case)
  # when doing a transparent upgrade.  The last worker spawned
  # will then kill off the old master process with a SIGQUIT.

  # old_pid = "#{server.config[:pid]}.oldbin"
  # if old_pid != server.pid
  #   begin
  #     sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
  #     Process.kill(sig, File.read(old_pid).to_i)
  #   rescue Errno::ENOENT, Errno::ESRCH
  #   end
  # end


  # Throttle the master from forking too quickly by sleeping.  Due
  # to the implementation of standard Unix signal handlers, this
  # helps (but does not completely) prevent identical, repeated signals
  # from being lost when the receiving process is busy.
  # sleep 1

end

after_fork do |server, worker|
  # per-process listener ports for debugging/admin/migrations
  # addr = "127.0.0.1:#{9293 + worker.nr}"
  # server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true)

  # the following is *required* for Rails + "preload_app true",
  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.establish_connection

  # if preload_app is true, then you may also want to check and
  # restart any other shared sockets/descriptors such as Memcached,
  # and Redis.  TokyoCabinet file handles are safe to reuse
  # between any number of forked children (assuming your kernel
  # correctly implements pread()/pwrite() system calls)
end

nginx.conf

# This goes to /etc/nginx/nginx.conf (or where nginx is installed)

user www-data;
worker_processes 2;
pid /var/run/nginx.pid;

events {
  worker_connections 768;
}

http {

  ##
  # Basic Settings
  ##

  sendfile on;
  tcp_nopush on;
  tcp_nodelay off;
  keepalive_timeout 65;
  types_hash_max_size 2048;
  server_tokens off;

  # server_names_hash_bucket_size 64;
  # server_name_in_redirect off;

  include /etc/nginx/mime.types;
  default_type application/octet-stream;

  ##
  # Logging Settings
  ##

  access_log /var/log/nginx/access.log;
  error_log /var/log/nginx/error.log;

  ##
  # Gzip Settings
  ##

  gzip on;
  gzip_disable "msie6";
  gzip_vary on;
  gzip_proxied any;
  gzip_min_length 500;
  gzip_http_version 1.1;
  gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

  ##
  # Unicorn Rails
  ##

  upstream unicorn {
    server unix:/your_project/current/tmp/sockets/unicorn.sock fail_timeout=0;
  }

  ##
  # Virtual Host Configs
  ##
  include /etc/nginx/sites-enabled/*;

}

default

# This file goes to /etc/nginx/sites-available/default
# Then create a soft symlink of this file in /etc/nginx/sites-enabled/default

server {
  listen 80;
  server_name your_domain.name;
  root /your_project/current/public;

  client_max_body_size 1G;

  keepalive_timeout 300;

  try_files $uri/index.html $uri.html $uri @unicorn;

  location @unicorn {
    proxy_set_header X-Forward-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_set_header X-Forward_Proto $scheme;
    proxy_redirect off;
    # This passes requests to unicorn, as defined in /etc/nginx/nginx.conf
    proxy_pass http://unicorn;
    proxy_read_timeout 300s;
    proxy_send_timeout 300s;
  }

  # You can override error pages by redirecting the requests to a file in your
  # application's public folder, if you so desire:

  error_page 500 502 503 504 /500.html;
  location = /500.html {
    root /your_project/current/public;
  }

  error_page 404 /404.html;
    location = /404.html {
    root /your_project/current/public;
  }
}

There are lots of instructions of the server configuration procedure because servers run in various environments, the code above showcase the most typical configuration from my perspective.

After complete all these configurations, restart nginx server and deploy your code again, then your site should properly running now.