Skip to content

Instantly share code, notes, and snippets.

@grammakov
Forked from stevenchanin/digitalocean.md
Last active August 29, 2015 14:06
Show Gist options
  • Save grammakov/c268a5a1eed9931f3a2d to your computer and use it in GitHub Desktop.
Save grammakov/c268a5a1eed9931f3a2d to your computer and use it in GitHub Desktop.

Deploy Rails app to digitalocean with nginx, unicorn, capistrano & postgres

Create Droplet

Create droplet of your liking (ubuntu 12.10 x32)

Setup DNS

On Digital Ocean, create a DNS entry for your server (xyz.com)

Make sure it has NS records that use digital oceans nameservers

NS1.DIGITALOCEAN.COM.
NS2.DIGITALOCEAN.COM.
NS3.DIGITALOCEAN.COM.

Setup a generic A record with your IP address

@ 123.123.123.123

Setup useful subdomains as A records

www 123.123.123.123
mail 123.123.123.123
smtp 123.123.123.123

Setup an MX record

10 mail.xyz.com.

(note the . at the end)

Setup an SPF record (type = TXT)

@ "v=spf1 ip4:123.123.123.123 ~all"

Configure SSH / Sudo

ssh to root in terminal with your server ip

ssh root@123.123.123.123

Add ssh fingerprint and enter password provided in email

Change password

passwd

Create new user

adduser deploy

Set new users privileges

visudo

Find user privileges section

# User privilege specification
root  ALL=(ALL:ALL) ALL

Add your new user privileges under root & cntrl+x then y to save

deploy ALL=(ALL:ALL) ALL

Configure SSH

nano /etc/ssh/sshd_config

Find and change port to one that isn't default(22 is default: choose between 1025 ..65536)

Port 22 # change this to whatever port you wish to use
Protocol 2
PermitRootLogin no

PasswordAuthentication no
ChallengeResponseAuthentication no

Add to bottom of sshd_config file after changing port (cntrl+x then y to save)

UseDNS no
AllowUsers deploy

Reload ssh

reload ssh

Don't close root! Open new shell and ssh to vps with new username(remember the port, or you're locked out!)

ssh -p 1026 deploy@123.123.123.123

Install Ruby

Update packages on virtual server

sudo apt-get update
sudo apt-get install curl

install latest stable version of rvm

curl -L get.rvm.io | bash -s stable

NOTE: There is currently (7/23/13) a problem with RVM 1.21.13 that was causing it report problems with the PATH that didn't exist. This blows up capistrano later. The solution is

rvm get 1.21.12

load rvm

source ~/.rvm/scripts/rvm

install rvm dependencies

rvm requirements

Install ruby 2.0.0

rvm install 2.0.0

Use 2.0.0 as rvm default

rvm use 2.0.0 --default

Install Rails and Core Gems

install latest version of rubygems if rvm install didn't

rvm rubygems current

install rails gem

gem install rails --no-ri --no-rdoc

Install postgres

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

Create new postgres user

sudo -u postgres psql
create user deploy with password 'password';
alter role deploy superuser createrole createdb replication;
create database projectname_production owner deploy;

Install git-core

sudo apt-get install git-core

I ultimately needed some other low level libraries for Nokogiri and Imagemagick to build

sudo apt-get install libxslt-dev libxml2-dev
sudo apt-get install imagemagick libmagickcore-dev libmagickwand-dev

Install bundler

gem install bundler --no-ri

setup nginx

sudo apt-get install nginx
nginx -h
cat /etc/init.d/nginx
/etc/init.d/nginx -h
sudo service nginx start
cd /etc/nginx

On the development machine, edit the Gemfile to include unicorn.

Gemfile

group :production do
  gem 'unicorn'
end

then create placeholders for the confirmation files

touch config/unicorn.rb
touch config/unicorn_init.sh file
touch config/nginx.conf

chmod +x config/unicorn_init.sh

config/nginx.conf (change projectname and username to match your directory structure!)

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

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

  location ^~ /assets/ {
    gzip_static on;
    expires max;
    add_header Cache-Control 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;
}

config/unicorn.rb

root = "/home/deploy/apps/projectname/current"
working_directory root
pid "#{root}/tmp/pids/unicorn.pid"
stderr_path "#{root}/log/unicorn.log"
stdout_path "#{root}/log/unicorn.log"

listen "/tmp/unicorn.projectname.sock"
worker_processes 2
timeout 30

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

config/unicorn_init.sh

#!/bin/sh
### BEGIN INIT INFO
# Provides:          unicorn
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Manage unicorn server
# Description:       Start, stop, restart unicorn server for a specific application.
### END INIT INFO
set -e

# Feel free to change any of the following variables for your app:
TIMEOUT=${TIMEOUT-60}
APP_ROOT=/home/deploy/apps/projectname/current
PID=$APP_ROOT/tmp/pids/unicorn.pid
CMD="cd $APP_ROOT; bundle exec unicorn -D -c $APP_ROOT/config/unicorn.rb -E production"
AS_USER=deploy
set -u

OLD_PIN="$PID.oldbin"

sig () {
  test -s "$PID" && kill -$1 `cat $PID`
}

oldsig () {
  test -s $OLD_PIN && kill -$1 `cat $OLD_PIN`
}

run () {
  if [ "$(id -un)" = "$AS_USER" ]; then
    eval $1
  else
    su -c "$1" - $AS_USER
  fi
}

case "$1" in
start)
  sig 0 && echo >&2 "Already running" && exit 0
  run "$CMD"
  ;;
stop)
  sig QUIT && exit 0
  echo >&2 "Not running"
  ;;
force-stop)
  sig TERM && exit 0
  echo >&2 "Not running"
  ;;
restart|reload)
  sig HUP && echo reloaded OK && exit 0
  echo >&2 "Couldn't reload, starting '$CMD' instead"
  run "$CMD"
  ;;
upgrade)
  if sig USR2 && sleep 2 && sig 0 && oldsig QUIT
  then
    n=$TIMEOUT
    while test -s $OLD_PIN && test $n -ge 0
    do
      printf '.' && sleep 1 && n=$(( $n - 1 ))
    done
    echo

    if test $n -lt 0 && test -s $OLD_PIN
    then
      echo >&2 "$OLD_PIN still exists after $TIMEOUT seconds"
      exit 1
    fi
    exit 0
  fi
  echo >&2 "Couldn't upgrade, starting '$CMD' instead"
  run "$CMD"
  ;;
reopen-logs)
  sig USR1
  ;;
*)
  echo >&2 "Usage: $0 <start|stop|restart|upgrade|force-stop|reopen-logs>"
  exit 1
  ;;
esac

Add capistrano and rvm capistrano to gemfile

gem 'capistrano'
gem 'rvm-capistrano'

Create capfile & config/deploy.rb files

capify .

deploy.rb (remember to change the port to whatever number you used for ssh)

require "bundler/capistrano"
require "rvm/capistrano"

server "123.123.123.123", :web, :app, :db, primary: true

set :application, "projectname"
set :user, "deploy"
set :port, 1026
set :deploy_to, "/home/#{user}/apps/#{application}"
set :deploy_via, :remote_cache
set :use_sudo, false

set :scm, "git"
set :repository, "git@github.com:username/#{application}.git"
set :branch, "master"

default_run_options[:pty] = true
ssh_options[:forward_agent] = true

after "deploy", "deploy:cleanup" # keep only the last 5 releases

namespace :deploy do
  %w[start stop restart].each do |command|
    desc "#{command} unicorn server"
    task command, roles: :app, except: {no_release: true} do
      run "/etc/init.d/unicorn_#{application} #{command}"
    end
  end

  task :setup_config, roles: :app do
    sudo "ln -nfs #{current_path}/config/nginx.conf /etc/nginx/sites-enabled/#{application}"
    sudo "ln -nfs #{current_path}/config/unicorn_init.sh /etc/init.d/unicorn_#{application}"
    run "mkdir -p #{shared_path}/config"
    put File.read("config/database.example.yml"), "#{shared_path}/config/database.yml"
    put File.read("config/initializers/secret_token.example.rb"), "#{shared_path}/config/secret_token.rb"
    puts "Now edit the config files in #{shared_path}."
  end
  after "deploy:setup", "deploy:setup_config"

  task :symlink_config, roles: :app do
    run "ln -nfs #{shared_path}/config/database.yml #{release_path}/config/database.yml"
    run "ln -nfs #{shared_path}/config/secret_token.rb #{release_path}/config/initializers/secret_token.rb"
  end
  after "deploy:finalize_update", "deploy:symlink_config"

  desc "Make sure local git is in sync with remote."
  task :check_revision, roles: :web do
    unless `git rev-parse HEAD` == `git rev-parse origin/master`
      puts "WARNING: HEAD is not the same as origin/master"
      puts "Run `git push` to sync changes."
      exit
    end
  end
  before "deploy", "deploy:check_revision"
end

Capfile

load 'deploy'
load 'deploy/assets'
load 'config/deploy'

Shake hands with github

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

Add ssh key to digitalocean

cat ~/.ssh/id_rsa.pub | ssh -p 1026 deploy@123.123.123.123 'cat >> ~/.ssh/authorized_keys'

Create repo and push to github

# Add config/database.yml to .gitignore
cp config/database.yml config/database.example.yml
git init
git add .
git commit -m "Inital Commit"
git remote add origin git@github.com:username/reponame
git push origin master

deployment

cap deploy:setup

edit shared configuration files on server

emacs /home/deploy/apps/projectname/shared/config/database.yml
emacs /home/deploy/apps/projectname/shared/config/initializers/secret_token.rb

then finish deploying

cap deploy:cold

after deploy:cold

sudo rm /etc/nginx/sites-enabled/default
sudo service nginx restart
sudo update-rc.d -f unicorn_projectname defaults

Make changes and deploy

# Make changes
git add .
git commit -m "Changes"
git push origin master
cap deploy

Resources from Railscasts/digital ocean documentation. For use if puppet or chef is a little over your head. I know you can spawn up a rails app using nginx and unicorn with mysql, but you don't learn much that way!

Hopefully I didn't miss any steps, although I'm sure I did. Please leave comments if you run into troubles.

Setting Up Command Line Mail

sudo apt-get install mailutils

Setting Up Postfix

https://www.digitalocean.com/community/articles/how-to-install-and-setup-postfix-on-ubuntu-12-04

Install

sudo apt-get install postfix

not sure if you need to to a reconfigure to set all the options

sudo dpkg-reconfigure postfix

During the installation, you will see a dialogue box appear, asking you which kind of installation you would prefer. Select “Internet Site”.

Follow up by entering the name of your domain.

Once Postfix is installed there are a few steps that need to be taken before it is fully functional.

Configure

Once Postfix is installed, go ahead and open the main configuration file.

sudo nano /etc/postfix/main.cf

There are a few changes that should be made in this file.

myhostname = example.com

Put in name of your domain into myhostname.

If you want to have mail forwarded to other domains, replace alias_maps with virtual_alias_maps and point it to /etc/postfix/virtual.

virtual_alias_maps = hash:/etc/postfix/virtual

To just use simple aliases, add anything else you want to /etc/aliases. For example, to have emails sent to root be redirected to you (e.g. root@example.com ==> you@example.com) add the following line to /etc/aliases

root:          you
contact:       you

once you've added all your aliases,

newaliases

The rest of the entries are described below

mydestination defines the domains that postfix is going to serve, in this case—localhost and your domain (eg. example.com).

relayhost can be left, as is the default, empty.

mynetworks defines who can use the mail server. This should be set to local—creating an open mail server is asking for SPAM. This will usually have damaging effects on your server and may put you in line for discipline from your web hosting provider.

If it is not set up by default, as it should be, make sure you have the following text on that line:

mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128

To turn on encryped imap

in the main.cf file, add the following
smtpd_sasl_type = dovecot
# Can be an absolute path, or relative to $queue_directory
# Debian/Ubuntu users: Postfix is setup by default to run chrooted, so it is best to leave it as-is below
smtpd_sasl_path = private/auth
# and the common settings to enable SASL:
smtpd_sasl_auth_enable = yes
smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, \
	reject_unauth_destination

The rest of the lines are set by default.

Enable ports other than 25

In the /etc/postfix/master.cf look and uncomment:

submission inet n       -       -       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
  -o milter_macro_daemon_name=ORIGINATING

Save, exit, and reload the configuration file to put your changes into effect:

sudo /etc/init.d/postfix reload

#Install Dovecot

sudo apt-get install dovecot-imapd dovecot-pop3d

edit /etc/dovecot/dovecot.conf

protocols = pop3 pop3s imap imaps
mail_location = mbox:~/mail:INBOX=/var/mail/%u

and then restart dovcot

sudo service dovecot restart

Test

To check that it is running, type the command

ps -A | grep dovecot

then telnet in

sudo apt-get install telnet
telnet localhost pop3
telnet localhost imap2

you should see something like

If you see something like the following, the installation has been successful.

matt@kalliope:~$ telnet localhost pop3
Trying localhost...
Connected to localhost.
Escape character is '^]'.
+OK dovecot ready.

Authentication

edit /etc/dovecot/dovecot.conf

ssl = yes

ssl_cert_file = /etc/ssl/certs/ssl-cert-snakeoil.pem
ssl_key_file = /etc/ssl/private/ssl-cert-snakeoil.key

add a A Record to the DNS (with name = mail and hostname = your IP)
add an A Record to the DNS (with name = smtp and hostname = your IP)

Turn on authentication

Add the following to the bottom of dovecot.conf

auth default {
  mechanisms = plain login
  passdb pam {
  }
  userdb passwd {
  }
  socket listen {
    client {
      # Assuming the default Postfix $queue_directory setting
      path = /var/spool/postfix/private/auth
      mode = 0660
      # Assuming the default Postfix user and group
      user = postfix
      group = postfix
    }
    # deliver and some other programs need also auth-master:
    #master {
    #  path = /var/run/dovecot/auth-master
    #  mode = 0600
    #}
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment