Setting up a Digital Ocean Droplet (VPS) on Ubuntu with Rails, Nginx, Unicorn, Postgres, Redis and Capistrano
Create a Droplet on Digital Ocean
I stuck with the defaults:
Size
512MB / 1 CPU
20GB SSD
$5 / Month
ssh to root in terminal with your server's ip address
Add ssh fingerprint and enter the password provided in the email from DO
Create new user. I like calling this guy "deployer" so that's what you'll see throughout this post.
Find user privileges section and add your new user privileges under root. Type cntrl+x then y to save
# User privilege specification root ALL=(ALL:ALL) ALL deployer ALL=(ALL:ALL) ALL
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 I'm using 1026 for this example Port 1026 Protocol 2 PermitRootLogin yes # You can change this to "no" if you don't want the root user to be able to ssh in anymore
Add to bottom of sshd_config file after changing port (cntrl+x then y to save)
UseDNS no AllowUsers deployer # Only add this line if you have changed PermitRootLogin to no, or you can do AllowUsers deployer root
Don't close root! Open a new shell and ssh to your VPS (droplet) with deployer (remember the port you set above or you'll be locked out)
Update the packages as deployer on your VPS. This installs curl, some python stuff, git and node.js (node.js is for the Rails asset pipeline)
sudo apt-get update sudo apt-get -y install curl python-software-properties git-core nodejs
sudo apt-get -y install build-essential tcl8.5
Change to your home directory and download Redis with wget (I use it for Sidekiq)
cd ~ wget http://redis.googlecode.com/files/redis-2.4.16.tar.gz
Untar Redis, switch into its directory then run make and make install. Then run the Redis install script.
tar xzf redis-2.4.16.tar.gz cd redis-2.4.16 make make test sudo make install cd utils sudo ./install_server.sh
To automatically start Redis when the server boots
sudo update-rc.d redis_6379 defaults
Install latest stable version of rvm
curl -L get.rvm.io | bash -s stable
source ~/.rvm/scripts/rvm
Install latest version of rubygems if rvm install didn't
gem install rails --no-ri --no-rdoc
sudo apt-get install postgresql postgresql-server-dev-9.1 gem install pg -- --with-pg-config=/usr/bin/pg_config
Create a new postgres user. It's not a bad idea to use the same username here. Remember these so you can add them to your database.yml file later.
sudo -u postgres psql create user deployer with password 'secret'; alter role deployer superuser createrole createdb replication; create database projectname_production owner deployer; \q
sudo apt-get install nginx
These 3 commands test if nginx is installed
nginx -h cat /etc/init.d/nginx /etc/init.d/nginx -h
sudo service nginx start # cd /etc/nginx
Preparing for Deployment locally with Unicorn, Nginx and Capistrano
Add unicorn to the gemfile
Manually create these 3 files:
config/nginx.conf (change projectname and username, I'm usingdeployer, 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/deployer/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; }
root = "/home/deployer/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
#!/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/deployer/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=deployer 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
Change permissions on unicornt_init.sh
chmod +x config/unicorn_init.sh
Add capistrano and rvm capistrano to gemfile
gem 'capistrano' gem 'rvm-capistrano'
Create Capfile & config/deploy.rb files by running
For deploy.rb you should change the server ip address, :user (I'm using deployer), :repository path (gertig), and note that :port is not 22 since we changed it above to 1026
require "bundler/capistrano" require "rvm/capistrano" require 'sidekiq/capistrano' server "123.123.123.123", :web, :app, :db, primary: true set :application, "projectname" set :user, "deployer" set :port, 1026 set :deploy_to, "/home/#{user}/apps/#{application}" set :deploy_via, :remote_cache set :use_sudo, false set :scm, "git" set :repository, "[email protected]:gertig/reponame.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" puts "Now edit #{shared_path}/config/database.yml and add your username and password" 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" 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
load 'deploy' load 'deploy/assets' load 'config/deploy'
SSH in to Github to add your keys
# follow the steps in this guide if receive permission denied(public key) # https://help.github.com/articles/error-permission-denied-publickey ssh [email protected]
Add ssh key to digitalocean
cat ~/.ssh/id_rsa.pub | ssh -p 1026 [email protected] 'cat >> ~/.ssh/authorized_keys'
Add your Environment variables like ENV["SECRET_KEY"]
sudo nano /etc/environment
On each new line add something like
SECRET_TOKEN=55555DDDDDDDEEEE44444 REDIS_URL=redis://123.123.123.123:6379
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 [email protected]:gertig/reponame git push origin master
Edit database.yml on server to add username and password to the production database
nano apps/projectname/shared/config/database.yml
Then remove the nginx default folder (if it doesn't exist don't worry about it)
sudo rm /etc/nginx/sites-enabled/default
sudo service nginx restart sudo update-rc.d -f unicorn_projectname defaults
Now whenever you make changes do the following.
# Make changes git add . git commit -m "Changes" git push origin master
Stop and Start the unicorn server
sudo service unicorn_appname stop sudo service unicorn_appname start
sudo service nginx restart
If Nokogiri complains about libxml run this:
sudo apt-get install libxslt-dev libxml2-dev
Then try your cap deploy:cold again if that is where you failed.