Skip to content

Instantly share code, notes, and snippets.

@sahilchopra
Forked from ChuckJHardy/digital_ocean_setup.md
Last active July 27, 2016 06:16
Show Gist options
  • Save sahilchopra/ca1c65f154b40be97c71b0111cfa09ab to your computer and use it in GitHub Desktop.
Save sahilchopra/ca1c65f154b40be97c71b0111cfa09ab to your computer and use it in GitHub Desktop.
DigitalOcean Ubuntu 14.04 x64 + Rails 4 + Nginx + Unicorn + PostgreSQL + Capistrano 3 Setup Instructions
The nginx install process on ubuntu creates a folder at /etc/nginx. In this folder you’ll find a few configuration files, along with a sites-available and a sites-enabled folder. The sites-available folder is where you’ll configure your application-specific nginx setup, and /etc/nginx/nginx.conf is most likely where you’ll configure global nginx settings. The following is a sensibly configured nginx.conf.
--------------------------------------------------------------------------------------------------------------------
# Run nginx as www-data.
user www-data;
# One worker process per CPU core is a good guideline.
worker_processes 1;
# The pidfile location.
pid /var/run/nginx.pid;
# For a single core server, 1024 is a good starting point. Use `ulimit -n` to
# determine if your server can handle more.
events {
worker_connections 1024;
}
http {
##
# Basic Settings
##
sendfile on;
tcp_nopush on;
tcp_nodelay off;
keepalive_timeout 65;
types_hash_max_size 2048;
server_tokens 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_http_version 1.1;
gzip_proxied any;
gzip_min_length 500;
gzip_types text/plain text/xml text/css
text/comma-separated-values text/javascript
application/x-javascript application/atom+xml;
##
# Unicorn Rails
##
# The socket here must match the socket path that you set up in unicorn.rb.
upstream unicorn {
server unix:/app/path/tmp/unicorn-staging.sock fail_timeout=0;
}
##
# Virtual Host Configs
##
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
server {
listen 80;
server_name mysite.com; # Replace this with your site's domain.
keepalive_timeout 300;
client_max_body_size 4G;
root /app/root/public; # Set this to the public folder location of your Rails application.
try_files $uri/index.html $uri.html $uri @unicorn;
location @unicorn {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded_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 /app/root/public;
}
}
--------------------------------------------------------------------------------------------------------------------------
user capbeast;
worker_processes 4;
# the limit for the maximum FDs on the server is usually set by the OS.
# if you don't set FD's then OS settings will be used which is by default 2000
worker_rlimit_nofile 100000;
events {
worker_connections 2048;
# optmized to serve many clients with each thread, essential for linux
use epoll;
# accept as many connections as possible, may flood worker connections if set too low
multi_accept on;
}
http {
# Hide nginx version information.
server_tokens off;
# Define the MIME types for files.
include mime.types;
default_type application/octet-stream;
# Update charset_types due to updated mime.types
charset_types text/xml text/plain text/vnd.wap.wml application/x-javascript application/rss+xml text/css application/javascript application/json;
# Format to use in log files
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
# Speed up file transfers by using sendfile() to copy directly
# between descriptors rather than using read()/write().
sendfile on;
# How long to allow each connection to stay idle; longer values are better
# for each individual client, particularly for SSL, but means that worker
# connections are tied up longer. (Default: 65)
keepalive_timeout 65;
# Tell Nginx not to send out partial frames; this increases throughput
# since TCP frames are filled up before being sent out. (adds TCP_CORK)
tcp_nopush on;
gzip on;
gzip_min_length 100;
gzip_vary on;
gzip_disable "MSIE [1-6]\.";
gzip_buffers 128 128k;
gzip_types
application/atom+xml
application/javascript
application/json
application/rss+xml
application/vnd.ms-fontobject
application/x-font-ttf
application/x-web-app-manifest+json
application/xhtml+xml
application/xml
font/opentype
image/svg+xml
image/x-icon
text/css
text/plain
text/x-component;
server_names_hash_bucket_size 64;
passenger_root /usr/local/rvm/gems/ruby-2.1.2/gems/passenger-4.0.45;
passenger_ruby /usr/local/rvm/gems/ruby-2.1.2/wrappers/ruby;
passenger_max_pool_size 12;
passenger_min_instances 12;
# Cloudflare
# set_real_ip_from 199.27.128.0/21;
# set_real_ip_from 173.245.48.0/20;
# set_real_ip_from 103.21.244.0/22;
# set_real_ip_from 103.22.200.0/22;
# set_real_ip_from 103.31.4.0/22;
# set_real_ip_from 141.101.64.0/18;
# set_real_ip_from 108.162.192.0/18;
# set_real_ip_from 190.93.240.0/20;
# set_real_ip_from 188.114.96.0/20;
# set_real_ip_from 197.234.240.0/22;
# set_real_ip_from 198.41.128.0/17;
# set_real_ip_from 162.158.0.0/15;
# set_real_ip_from 104.16.0.0/12;
# set_real_ip_from 2400:cb00::/32;
# set_real_ip_from 2606:4700::/32;
# set_real_ip_from 2803:f800::/32;
# set_real_ip_from 2405:b500::/32;
# set_real_ip_from 2405:8100::/32;
# real_ip_header CF-Connecting-IP;
#dev.capbeast.com block
server {
listen 80;
server_name dev.capbeast.com;
root /home/capbeast/capbeast_development/current/public;
# auth_basic "Restricted";
# auth_basic_user_file /home/capbeast/capbeast/.htpasswd;
client_body_in_file_only clean;
client_body_buffer_size 128K;
client_max_body_size 1G;
send_timeout 1000s;
passenger_enabled on;
rails_env production;
}
server {
server_name www.capbeast.com;
rewrite ^/(.*) https://capbeast.com/$1 permanent;
}
# server {
# listen 80;
# server_name localhost;
# access_log off;
# allow 127.0.0.1;
# deny all;
# location /nginx_status {
# stub_status on;
# }
# }
server {
listen 80;
server_name capbeast.com;
return 301 https://$server_name$request_uri;
}
# HTTPS server
server {
listen 443 ssl;
server_name capbeast.com designer.capbeast.com;
root /home/capbeast/capbeast/current/public;
# include h5bp/basic.conf;
client_body_in_file_only clean;
client_body_buffer_size 128K;
client_max_body_size 1G;
send_timeout 1000s;
# location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
# expires 3d;
# }
pagespeed on;
pagespeed FileCachePath /var/ngx_pagespeed_cache; # Use tmpfs for best results.
location ~ "\.pagespeed\.([a-z]\.)?[a-z]{2}\.[^.]{10}\.[^.]+" { add_header "" ""; }
location ~ "^/ngx_pagespeed_static/" { }
location ~ "^/ngx_pagespeed_beacon$" { }
location /ngx_pagespeed_statistics { allow 127.0.0.1; deny all;}
location /ngx_pagespeed_global_statistics { allow 127.0.0.1; deny all;}
location /ngx_pagespeed_message { allow 127.0.0.1; deny all;}
location /pagespeed_console { allow 127.0.0.1; deny all;}
if ($host !~* ^(capbeast.com|www.capbeast.com|designer.capbeast.com)$ ) {
return 405;
}
ssl on;
ssl_certificate /etc/ssl/certs/capbeast_ssl_certs/bundle.crt;
ssl_certificate_key /etc/ssl/certs/capbeast_ssl_certs/capbeast.key;
passenger_enabled on;
rails_env production;
}
}

DigitalOcean Ubuntu 14.04 x64 + Rails 4 + Nginx + Unicorn + PostgreSQL + Capistrano 3

SSH into Root

$ ssh [email protected]

Change Root Password

$ passwd

Add Deploy User

$ adduser deployer

Update Sudo Privileges

$ visudo
username ALL=(ALL:ALL) ALL

Configure SSH

$ vi /etc/ssh/sshd_config
Port 22 # Change (1025..65536)
Protocol 2 # Change
PermitRootLogin no  # Change
UseDNS no # Add
AllowUsers deployer # Add

Reload SSH

$ reload ssh

Make sure your system is up to date sudo apt-get update && apt-get upgrade

SSH with Deploy User (Don't close root)

$ ssh -p 1026 [email protected]

Install Curl

$ sudo apt-get update
$ sudo apt-get install curl

Install 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.3.1
rvm use 2.3.1 --default
ruby -v

We'll be using that to setup our production server because it's very easy to setup.

sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 561F9B9CAC40B2F7
sudo apt-get install -y apt-transport-https ca-certificates

sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 561F9B9CAC40B2F7
sudo apt-get install -y apt-transport-https ca-certificates

Add Passenger APT repository

sudo sh -c 'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger xenial main > /etc/apt/sources.list.d/passenger.list'
sudo apt-get update

Install Passenger & Nginx

sudo apt-get install -y nginx-extras passenger

Install nginx

sudo apt-get install nginx

Install PostgreSQL

$ sudo apt-get install postgresql postgresql-server-dev-9.3
$ gem install pg -- --with-pg-config=/usr/bin/pg_config

Create Postgres User

$ sudo -u postgres psql
create user deployer with password 'password';
alter role deployer superuser createrole createdb replication;
create database MYAPP_production owner deployer;

Install GIT

$ sudo apt-get install git-core

Install Bundler

$ gem install bundler

Setup Nginx

$ sudo apt-get install nginx
$ nginx -h
$ cat /etc/init.d/nginx
$ /etc/init.d/nginx -h
$ sudo service nginx start
$ sudo vim /etc/nginx/sites-enabled/default

upstream unicorn {
  server unix:/tmp/unicorn.MYAPP.sock fail_timeout=0;
}

server {
  listen 80 default deferred;
  # server_name example.com;
  root /home/deployer/apps/MYAPP/current/public;

  location ^~ /assets/ {
    gzip_static on;
    expires max;
    add_header Cache-Control public;
  }

  location ~ ^/(robots.txt|sitemap.xml.gz)/ {
    root /home/deployer/apps/MYAPP/current/public;
  }

  try_files $uri/index.html $uri @unicorn;
  location @unicorn {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://unicorn;
  }

  error_page 500 502 503 504 /500.html;
  client_max_body_size 4G;
  keepalive_timeout 10;
}

Add Unicorn

$ vim Gemfile
gem 'unicorn'    

Update Unicorn Config

$ vim config/unicorn/production.rb

root = "/home/deployer/apps/MYAPP/current"
working_directory root

pid "#{root}/tmp/pids/unicorn.pid"

stderr_path "#{root}/log/unicorn.log"
stdout_path "#{root}/log/unicorn.log"

worker_processes Integer(ENV['WEB_CONCURRENCY'])
timeout 30
preload_app true

listen '/tmp/unicorn.spui.sock', backlog: 64

before_fork do |server, worker|
  Signal.trap 'TERM' do
    puts 'Unicorn master intercepting TERM and sending myself QUIT instead'
    Process.kill 'QUIT', Process.pid
  end

  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.connection.disconnect!
end

after_fork do |server, worker|
  Signal.trap 'TERM' do
    puts 'Unicorn worker intercepting TERM and doing nothing. Wait for master to send QUIT'
  end

  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.establish_connection
end

# Force the bundler gemfile environment variable to
# reference the capistrano "current" symlink
before_exec do |_|
  ENV['BUNDLE_GEMFILE'] = File.join(root, 'Gemfile')
end

Add Capistrano

$ vim Gemfile

group :development do
  gem 'capistrano-rails'
  gem 'capistrano-rvm'
  gem 'capistrano3-unicorn'
end

Install Capistrano

$ bundle exec cap install

Update Capistrano Capfile

$ vim Capfile

require 'capistrano/setup'
require 'capistrano/deploy'
require 'capistrano/rvm'
require 'capistrano/bundler'
require 'capistrano/rails/assets'
require 'capistrano/rails/migrations'
require 'capistrano3/unicorn'

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

Update Capistrano Deploy Config

$ vim config/deploy.rb

lock '3.3.5'

set :application, 'spui'
set :repo_url, '[email protected]:MYGITHUB/MYAPP.git'

ask :branch, proc { `git rev-parse --abbrev-ref HEAD`.chomp }.call

set :use_sudo, false
set :bundle_binstubs, nil
set :linked_files, fetch(:linked_files, []).push('config/database.yml')
set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system')

after 'deploy:publishing', 'deploy:restart'

namespace :deploy do
  task :restart do
    invoke 'unicorn:reload'
  end
end

Update Production Deploy Config

$ vim config/deploy/production.rb

set :port, 22
set :user, 'deployer'
set :deploy_via, :remote_cache
set :use_sudo, false

server '123.333.333.333',
  roles: [:web, :app, :db],
  port: fetch(:port),
  user: fetch(:user),
  primary: true

set :deploy_to, "/home/#{fetch(:user)}/apps/#{fetch(:application)}"

set :ssh_options, {
  forward_agent: true,
  auth_methods: %w(publickey),
  user: 'deployer',
}

set :rails_env, :production
set :conditionally_migrate, true    

Add SSH Key to DigitalOcean

$ cat ~/.ssh/id_rsa.pub | ssh -p 22 [email protected] 'cat >> ~/.ssh/authorized_keys'

Say Hi to Github

# follow the steps in this guide if receive permission denied(public key)
# https://help.github.com/articles/error-permission-denied-publickey
$ ssh [email protected]    

Check Deployment (Commit and Push)

$ cap production deploy:check

Deploy

$ cap production deploy

Configuring nginx.conf

The nginx install process on ubuntu creates a folder at /etc/nginx. In this folder you’ll find a few configuration files, along with a sites-available and a sites-enabled folder. The sites-available folder is where you’ll configure your application-specific nginx setup, and /etc/nginx/nginx.conf is most likely where you’ll configure global nginx settings. The following is a sensibly configured nginx.conf.

Run nginx as www-data.

user www-data;

One worker process per CPU core is a good guideline.

worker_processes 1;

The pidfile location.

pid /var/run/nginx.pid;

For a single core server, 1024 is a good starting point. Use ulimit -n to

determine if your server can handle more.

events { worker_connections 1024; }

http {

Basic Settings

sendfile on; tcp_nopush on; tcp_nodelay off; keepalive_timeout 65; types_hash_max_size 2048; server_tokens 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_http_version 1.1; gzip_proxied any; gzip_min_length 500; gzip_types text/plain text/xml text/css text/comma-separated-values text/javascript application/x-javascript application/atom+xml;

Unicorn Rails

The socket here must match the socket path that you set up in unicorn.rb.

upstream unicorn { server unix:/app/path/tmp/unicorn-staging.sock fail_timeout=0; }

Virtual Host Configs

include /etc/nginx/conf.d/.conf; include /etc/nginx/sites-enabled/; }

Configuring sites-available-sitename

server { listen 80; server_name mysite.com; # Replace this with your site's domain.

keepalive_timeout 300;

client_max_body_size 4G;

root /app/root/public; # Set this to the public folder location of your Rails application.

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

location @unicorn { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_set_header X-Forwarded_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 /app/root/public; } }


user capbeast;

worker_processes 4;

the limit for the maximum FDs on the server is usually set by the OS.

if you don't set FD's then OS settings will be used which is by default 2000

worker_rlimit_nofile 100000;

events {

worker_connections  2048;

# optmized to serve many clients with each thread, essential for linux
use epoll;

# accept as many connections as possible, may flood worker connections if set too low
multi_accept on;

}

http {

# Hide nginx version information.
server_tokens off;

# Define the MIME types for files.
include       mime.types;
default_type  application/octet-stream;

# Update charset_types due to updated mime.types
charset_types text/xml text/plain text/vnd.wap.wml application/x-javascript application/rss+xml text/css application/javascript application/json;

# Format to use in log files
log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                '$status $body_bytes_sent "$http_referer" '
                '"$http_user_agent" "$http_x_forwarded_for"';

# Speed up file transfers by using sendfile() to copy directly
# between descriptors rather than using read()/write().
sendfile        on;

# How long to allow each connection to stay idle; longer values are better
# for each individual client, particularly for SSL, but means that worker
# connections are tied up longer. (Default: 65)
keepalive_timeout 65;

# Tell Nginx not to send out partial frames; this increases throughput
# since TCP frames are filled up before being sent out. (adds TCP_CORK)
tcp_nopush      on;

gzip  on;

gzip_min_length 100;

gzip_vary on;

gzip_disable "MSIE [1-6]\.";

gzip_buffers 128 128k;

gzip_types
    application/atom+xml
    application/javascript
    application/json
    application/rss+xml
    application/vnd.ms-fontobject
    application/x-font-ttf
    application/x-web-app-manifest+json
    application/xhtml+xml
    application/xml
    font/opentype
    image/svg+xml
    image/x-icon
    text/css
    text/plain
    text/x-component;

server_names_hash_bucket_size  64;

passenger_root /usr/local/rvm/gems/ruby-2.1.2/gems/passenger-4.0.45;

passenger_ruby /usr/local/rvm/gems/ruby-2.1.2/wrappers/ruby;

passenger_max_pool_size 12;
passenger_min_instances 12;

Cloudflare

set_real_ip_from 199.27.128.0/21;

set_real_ip_from 173.245.48.0/20;

set_real_ip_from 103.21.244.0/22;

set_real_ip_from 103.22.200.0/22;

set_real_ip_from 103.31.4.0/22;

set_real_ip_from 141.101.64.0/18;

set_real_ip_from 108.162.192.0/18;

set_real_ip_from 190.93.240.0/20;

set_real_ip_from 188.114.96.0/20;

set_real_ip_from 197.234.240.0/22;

set_real_ip_from 198.41.128.0/17;

set_real_ip_from 162.158.0.0/15;

set_real_ip_from 104.16.0.0/12;

set_real_ip_from 2400:cb00::/32;

set_real_ip_from 2606:4700::/32;

set_real_ip_from 2803:f800::/32;

set_real_ip_from 2405:b500::/32;

set_real_ip_from 2405:8100::/32;

real_ip_header CF-Connecting-IP;

#dev.capbeast.com block

server {
    listen    80;
    server_name dev.capbeast.com;

    root   /home/capbeast/capbeast_development/current/public;

   # auth_basic "Restricted";
   # auth_basic_user_file /home/capbeast/capbeast/.htpasswd;

    client_body_in_file_only clean;
    client_body_buffer_size 128K;
    client_max_body_size 1G;
    send_timeout 1000s;

    passenger_enabled on;

    rails_env production;
}



server {
  server_name www.capbeast.com;
  rewrite ^/(.*) https://capbeast.com/$1 permanent;
}

server {

#   listen 80;
 #  server_name localhost;

 #  access_log off;
  # allow 127.0.0.1;
  # deny all;

# location /nginx_status {
 #   stub_status on;
 # }
# }

server {
   listen         80;
   server_name    capbeast.com;
   return         301 https://$server_name$request_uri;
}



# HTTPS server

server {

   listen       443 ssl;

    server_name  capbeast.com designer.capbeast.com;

    root   /home/capbeast/capbeast/current/public;

    # include h5bp/basic.conf;

    client_body_in_file_only clean;
client_body_buffer_size 128K;
client_max_body_size 1G;
send_timeout 1000s;

   # location ~*  \.(jpg|jpeg|png|gif|ico|css|js)$ {
    #  expires 3d;
   # }

pagespeed on;
pagespeed FileCachePath /var/ngx_pagespeed_cache;  # Use tmpfs for best results.

location ~ "\.pagespeed\.([a-z]\.)?[a-z]{2}\.[^.]{10}\.[^.]+" { add_header "" ""; }
location ~ "^/ngx_pagespeed_static/" { }
location ~ "^/ngx_pagespeed_beacon$" { }
location /ngx_pagespeed_statistics { allow 127.0.0.1; deny all;}
location /ngx_pagespeed_global_statistics { allow 127.0.0.1; deny all;}
location /ngx_pagespeed_message { allow 127.0.0.1; deny all;}
location /pagespeed_console {  allow 127.0.0.1; deny all;}

   if ($host !~* ^(capbeast.com|www.capbeast.com|designer.capbeast.com)$ ) {
      return 405;
     }

   ssl                  on;

    ssl_certificate      /etc/ssl/certs/capbeast_ssl_certs/bundle.crt;

    ssl_certificate_key  /etc/ssl/certs/capbeast_ssl_certs/capbeast.key;
    
    passenger_enabled on;
    
   rails_env production;

}

}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment