Skip to content

Instantly share code, notes, and snippets.

@jentanbernardus
Forked from stevenchanin/digitalocean.md
Created May 2, 2016 07:43
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save jentanbernardus/10f74d5626ba2381662deac6992f3e80 to your computer and use it in GitHub Desktop.
Save jentanbernardus/10f74d5626ba2381662deac6992f3e80 to your computer and use it in GitHub Desktop.
Setup instructions for Digital Ocean VPS using Ubuntu and Rails 3.2 #digitalocean #vps #setup

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

Create Droplet

Create droplet of your liking (ubuntu 12.04 x32) Use an xx.04 LTS version

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.

Setup DNS

Create Domain

In the DNS section of the admin interface, click (Add Domain) and pick your droplet. Then type in the domain name you want to set (e.g. devleverage.com)

This will automatically create an A record for @ and setup the Name Server entries.

Add Records

A Records

Add an A record for www with the VPS IP address Add an A record for mail with the VPS IP address

CNAME Records

Add a CNAME to the DNS (with name = smtp and hostname = @)

MX Records

Add an MX record with mail.. and the priority 10

for example, mail.devleverage.com. and 10 NOTE: the name has to end with a period

PTR Record

A PTR record is automatically created that resolves your IP back to the name of your droplet. You don't need to do anything.

SPF Record

Add a TXT record with (generated by Microsoft http://www.microsoft.com/mscorp/safety/content/technologies/senderid/wizard/)

v=spf1 mx ptr ip4:192.241.231.94 mx:mail.devleverage.com ~all

or (this may be enough)

v=spf1 ip4:192.241.231.94 ~all

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
home_mailbox = Maildir/

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

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

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 = maildir:/home/%u/Maildir

setup imap protocol to listen for non-localhost connections

edit /etc/dovecot/conf.d/20-imap.conf

protocol imap { listen = *:143 ssl_listen = *:993 ... }

pre-create the Maildir for future users

sudo maildirmake.dovecot /etc/skel/Maildir sudo maildirmake.dovecot /etc/skel/Maildir/.Drafts sudo maildirmake.dovecot /etc/skel/Maildir/.Sent sudo maildirmake.dovecot /etc/skel/Maildir/.Trash sudo maildirmake.dovecot /etc/skel/Maildir/.Templates

and for any existing users

sudo cp -r /etc/skel/Maildir /home// sudo chown -R : /home//Maildir sudo chmod -R 700 /home//Maildir

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

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