Skip to content

Instantly share code, notes, and snippets.

@adriendumont
Last active September 30, 2015 05:45
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save adriendumont/9840653d234643247e7e to your computer and use it in GitHub Desktop.
Save adriendumont/9840653d234643247e7e to your computer and use it in GitHub Desktop.
Digital Ocean(Ubuntu) - New Rails4 App deploy with Capistrano3, Unicorn, Nginx and MySQL

DigitalOcean(Ubuntu) - Rails 4, Capistrano3, Unicorn Nginx and MySQL

Step-by-step tutorial for deployment to Digital Ocean(Ubuntu 14.04)

Foreword

As a developer rarely handling deploy configurations, one of the most challenging problem when following a tutorial like this is making sure that everything fits together: unicorn config, ngingx config, deploy recipe and server configuration. So, to make it simpler, I'll define beforehand all constants that you need to consider and reference them across this tutorial.

DEPLOY_USER = "adriendumont"
REPO        = "git@github.com:adriendumont/sample-app-unicorn.git"
APP_NAME    = "sample_app_unicorn"
DO_IP       = "123.123.123.123"

This tutorial does not include steps to set-up your github account nor your ssh keys. If you need an app to go with your configuration, check this one out: sample-app-unicorn

Remote server set-up

From your DO console, create a new droplet and connect to it. To speed things up, make sure you have your shh keys linked to your account.

$ ssh root@#{DO_IP}

Create the user that you'll deploy with and give it privileges

$ adduser #{DEPLOY_USER}
$ visudo

# User privilege specification
root            ALL=(ALL:ALL) ALL
#{DEPLOY_USER}  ALL=(ALL:ALL) ALL

Configure ssh

$ vi /etc/ssh/sshd_config

PermitRootLogin no        # Change
AllowUsers #{DEPLOY_USER} # Add

$ reload ssh

Exit root and login as #{DEPLOY_USER}. Now we start preparing the environment

$ ssh #{DEPLOY_USER}@#{DO_IP}
$ sudo apt-get update
$ sudo apt-get install curl # install curl if you're missing it
$ sudo apt-get install git-core # install git if you're missing it

Install RVM, Ruby and dependencies

$ gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
$ \curl -sSL https://get.rvm.io | bash
$ source ~/.rvm/scripts/rvm
$ type rvm | head -1 # if the response is other than rvm is a function start debugging
$ rvm requirements
$ rvm install 2.2.2
$ rvm use 2.2.2 --default
$ gem install bundler

Install MySQL

$ sudo apt-get install mysql-server libmysqlclient-dev
$ mysql -u root -p
    > CREATE USER 'sample-app'@'localhost';
    > GRANT ALL PRIVILEGES ON * . * TO 'sample-app'@'localhost';
    > \q
$ mysql -u sample-app
    > CREATE DATABASE sample_app_production;
    > \q

Install Nginx

$ sudo apt-get install nginx
$ which nginx   # ensure nginx is installed
$ sudo service nginx start

Create ssh key and add it to your repo's deploy keys

$ ssh-keygen
$ cat ~/.ssh/id_rsa.pub

Project Set-up

Create a new Rails app:

$ rails new sample-app-unicorn -T --skip-bundle --database=mysql
$ cd sample-app-unicorn

Update the gemfile:

    gem "unicorn"

    group :development do
      gem 'capistrano',          require: false
      gem 'capistrano-rvm',      require: false
      gem 'capistrano-rails',    require: false
      gem 'capistrano-bundler',  require: false
      gem 'capistrano3-unicorn', require: false
    end

Set RVM gemset, install the gems(install rvm) and setup the database(install mysql)

$ rvm use 2.2.2@sample-app-unicorn --create
$ gem install bundler
$ bundle install
$ rake db:create && rake db:migrate

Capify the project

$ cap install

Edit the Capfile

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

require 'capistrano/bundler'
require 'capistrano/rails'
require 'capistrano/rvm'
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 }

Edit the capistrano deploy config:

config/deploy.rb

lock '3.4.0'

set :repo_url,        "#{REPO}"
set :application,     "#{APP_NAME}"
set :user,            "#{DEPLOY_USER}"
set :use_sudo,        false

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

set :bundle_binstubs, nil
set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache',
    'tmp/sockets', 'vendor/bundle', 'public/system') # OPTIONAL

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

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

Create a nginx.conf file inside your project

$ vim config/nginx.conf

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

server {
  listen 80 default deferred;
  # server_name example.com;
  root /home/#{DEPLOY_USER}/apps/#{APP_NAME}/current/public;

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

  location ~ ^/(robots.txt|sitemap.xml.gz)/ {
    root /home/#{DEPLOY_USER}/apps/#{APP_NAME}/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;
}

Create the unicorn config file

$ mkdir config/unicorn && vim config/unicorn/production.rb

root = "/home/#{DEPLOY_USER}/apps/#{APP_NAME}/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'] || 3)
timeout 30
preload_app true

listen "/tmp/unicorn.#{APP_NAME}.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

Update production deploy config

$ vim config/deploy/production.rb

set :port, 22
set :user, "#{DEPLOY_USER}"
set :deploy_via, :remote_cache
set :use_sudo, false

server "#{DO_IP}",
  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: fetch(:user)
}

set :rails_env, :production
set :conditionally_migrate, true

Update database.yml

production:
  <<: *default
  database: sample_app_production
  username: sample-app

Wrap everything up and push to git

$ git init
$ git remote add origin #{REPO}
$ git add .
$ git commit -m "initial commit"
$ git push origin master

Local set-up

Create ssh key and add it to the droplet's authorized keys

$ ssh-keygen
$ ssh-copy-id #{DEPLOY_USER}@#{DO_IP}

If you're on a Mac and don't have ssh-copy-id installed

$ brew install ssh-copy-id

or

$ sudo curl https://raw.githubusercontent.com/beautifulcode/ssh-copy-id-for-OSX/master/ssh-copy-id.sh -o /usr/local/bin/ssh-copy-id
$ sudo chmod +x /usr/local/bin/ssh-copy-id

Check deployment recipe. If successful deploy the project

$ cap production deploy:check
$ cap production deploy

Wrap up

If the deployment is successful, ssh to the VPS: $ ssh #{DEPLOY_USER}@#{DO_IP} $ sudo cp /home/#{DEPLOY_USER}/apps/#{APP_NAME}/current/config/nginx.conf /etc/nginx/sites-enabled/default $ sudo service nginx restart

For some reason, I am failing at setting up environment variables and now unicorn will show the following message:

E, [2015-07-30T10:02:47.199543 #7784] ERROR -- : app error: Missing `secret_token` and `secret_key_base` for 'production'
environment, set these values in `config/secrets.yml` (RuntimeError)

To fix this manually export the environment vars(or preferrably use something like Dotenv)

For this particular purpose, Ive added them inside .bash_profile

$ bundle exec rake secret # do it twice from app folder
$ sudo vim ~/.bash_profile
    export RAILS_ENV=production # Add
    export SECRET_TOKEN=one of the secrets # Add
    export SECRET_KEY_BASE=the other secret # Add, save and exit
$ source ~/.bash_profile

Your application should be up and running now! Go to #{DO_IP} and check it out! This set-up was last tested on 30/jul/2015.

References

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