Step-by-step tutorial for deployment to Digital Ocean(Ubuntu 14.04)
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 = "[email protected]: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
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
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
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
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.
- https://gist.github.com/ChuckJHardy/f44dda5f94c6bbdba9a4
- https://www.digitalocean.com/community/tutorials/initial-server-setup-with-ubuntu-14-04
- http://stackoverflow.com/questions/14635848/capistrano-commands-for-creating-database
- https://www.digitalocean.com/community/tutorials/a-basic-mysql-tutorial