Ruby on Rails, Nginx, Unicorn, Postgresql, Capistrano
This is a step-by-step guide for anyone who is trying to learn the basics of setting up Ruby on Rails, Nginx, Unicorn, and Postgresql to run on a VPS and use Capistrano to automate deployment. For this tutorial, we will use Digital Ocean as the VPS provider because it is cheap, fast, and simple to use.
Software versions used in this tutorial:
Ruby 2.1.3
Rails 4.2.0
Postgresql 9.3.6
Ubuntu 14.04 x64
Nginx 1.4.6
Unicorn 4.8.3
Capistrano 3.4.0
For reference, here is the git repo of this deploydemo app:
https://github.com/travisluong/deploydemo
Step 1: Preparation.
Spin up a Digital Ocean droplet with Ubuntu 14.04 x64.
Create a new repository on GitHub or Bitbucket. You’ll need this for Capistrano to pull from during deploys.
Step 2: Create a simple Rails app.
Create a new rails app. We will set the database to postgresql.
user@local $ rails new deploydemo -d postgresql
Commit and push app to git origin.
user@local $ cd deploydemo user@local $ git init user@local $ git add . user@local $ git commit -m "initial commit" user@local $ git remote add origin git@github.com:travisluong/deploydemo.git user@local $ git push origin master
Run bundle to install dependencies.
user@local $ bundle
Create postgresql database for development.
user@local $ createdb deploydemo_development
Create a rails scaffold
user@local $ bin/rails g scaffold post title content:text user@local $ bin/rake db:migrate
Set the root to the scaffold index in config/routes.rb.
root 'posts#index'
commit the changes.
user@local $ git add . user@local $ git commit -m “scaffold"
Step 3: Install and configure Capistrano and Unicorn.
Add these gems to Gemfile.
gem 'unicorn' group :development do gem 'capistrano-rails' gem 'capistrano-rvm' gem 'capistrano3-unicorn' end
Run bundle.
user@local $ bundle
Install Capistrano. This will create some files.
user@local $ cap install
In config/deploy.rb, set the application, repo_url, deploy_to with your settings. You can uncomment linked_files, and linked_dirs. You might also want to set format to pretty and log level to info to get rid of some unimportant (failed) messages from Capistrano.
set :application, 'deploydemo' set :repo_url, 'git@github.com:travisluong/deploydemo.git' set :deploy_to, '/var/www/deploydemo' set :linked_files, fetch(:linked_files, []).push('config/database.yml', 'config/secrets.yml') set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system') set :format, :pretty set :log_level, :info
Add require statements in Capfile. This will make it work with RVM and add extra rake tasks to Capistrano.
require 'capistrano/rails' require 'capistrano/rvm' require 'capistrano3/unicorn'
Add server to config/deploy/production.rb. You can find the server IP from your Digital Ocean dashboard. You’ll also want to set the unicorn_rack_env to production, since the gem uses “deployment” environment by default for some reason.
server '162.xxx.xxx.xx', user: 'deploy', roles: %w{app db web} set :unicorn_rack_env, -> { "production" }
Commit changes.
user@local $ git add . user@local $ git commit -m "capistrano"
Create a the unicorn configuration file in config/unicorn/production.rb.
working_directory '/var/www/deploydemo/current' pid '/var/www/deploydemo/current/tmp/pids/unicorn.pid' stderr_path '/var/www/deploydemo/log/unicorn.log' stdout_path '/var/www/deploydemo/log/unicorn.log' listen '/tmp/unicorn.deploydemo.sock' worker_processes 2 timeout 30 before_fork do |server, worker| old_pid = "/var/www/microsweepstakes/current/tmp/pids/unicorn.pid.oldbin" if old_pid != server.pid begin sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU Process.kill(sig, File.read(old_pid).to_i) rescue Errno::ENOENT, Errno::ESRCH end end end
Here is what this file is doing:
- Set the working directory of the app.
- Set the unicorn pid file location. That contains the id of the unicorn process.
- The unicorn log files.
- The socket file that connects to Nginx.
- The number of workers each master process will spawn.
- The amount of time a request is given before Unicorn kills the process.
- A before fork that kills the old process when a new process is started, so we can achieve zero downtime deploys.
Commit changes and push to origin.
user@local $ git add . user@local $ git commit -m "unicorn" user@local $ git push origin master
Step 4: Create a deploy user on your VPS.
ssh in to remote server.
user@local $ ssh root@162.xxx.xxx.xx
Create a deploy user and give sudo privileges. You can leave all the fields blank, except for password.
root@remote $ adduser deploy root@remote $ adduser deploy sudo
Switch to deploy user.
root@remote $ sudo su deploy
Step 5: Set up all of the SSH keys.
CD into home and make a .ssh directory.
deploy@remote $ cd deploy@remote $ mkdir .ssh
Copy your ssh key from local over to remote for password-less login.
user@local $ cat ~/.ssh/id_rsa.pub | ssh -p 22 deploy@162.xxx.xxx.xx 'cat >> ~/.ssh/authorized_keys'
Follow the instructions in the link below to add an ssh key to GitHub for your remote VPS. You need to do this so that Capistrano can pull the application from GitHub on deploys. The commands in this guide should be run on the VPS as your deploy user.
https://help.github.com/articles/generating-ssh-keys/
Step 6: Install packages and dependencies.
Run update.
deploy@remote $ sudo apt-get update
Install packages.
deploy@remote $ sudo apt-get install -y curl git-core build-essential zlib1g-dev libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 libcurl4-openssl-dev libxml2-dev libxslt1-dev python-software-properties
Install node.js for JavaScript runtime.
deploy@remote $ sudo apt-get install -y nodejs
Install Postgresql. libpq-dev is needed for building the pg gem later.
deploy@remote $ sudo apt-get install -y postgresql postgresql-contrib libpq-dev
Install nginx. If you navigate to your IP in your browser, you should see an nginx welcome page.
deploy@remote $ sudo apt-get install -y nginx
Step 7: Set up the postgres user and create production database.
Create the production database.
deploy@remote $ sudo -u postgres createdb deploydemo_production
Set up password for “postgres” user.
deploy@remote $ sudo -u postgres psql postgres=# \password postgres postgres=# \q
Step 8: Install RVM, ruby, and bundler.
Add a line to your .gemrc to turn off document generation. Document generation takes way too long.
deploy@remote $ echo "gem: --no-document" >> ~/.gemrc
Install rvm and ruby.
deploy@remote $ gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 deploy@remote $ \curl -sSL https://get.rvm.io | bash -s stable deploy@remote $ source /home/deploy/.rvm/scripts/rvm deploy@remote $ rvm install 2.1.3
Install bundler.
deploy@remote $ gem install bundler
Step 9: Create the directories and shared files needed for Capistrano deployment.
Make /var/www directory in remote.
deploy@remote $ sudo mkdir /var/www
Change the owner of /var/www to deploy.
deploy@remote $ sudo chown deploy /var/www
Make the shared config directory.
deploy@remote $ mkdir -p /var/www/deploydemo/shared/config
Make log directory for unicorn log.
deploy@remote $ mkdir -p /var/www/deploydemo/log
Create the shared database.yml file that will be shared between releases.
deploy@remote $ sudo vim /var/www/deploydemo/shared/config/database.yml
production: adapter: postgresql encoding: unicode pool: 5 timeout: 5000 database: deploydemo_production username: postgres password: password host: localhost
Run rake secret to generate a secret key. You will put that in the shared secrets.yml file.
user@local $ bin/rake secret
Create the shared secrets.yml file and put in the secret key you generated in the last step.
deploy@remote $ sudo vim /var/www/deploydemo/shared/config/secrets.yml
production: secret_key_base: 94d04182d80fe4ea1ec41b6839b019a02e8a3f8cfa0696ee3b5281d5512473c8483334b23f31bd7fcdf3914263d0719c819494613e3d6ffb1792a45b6277da66
Add the RAILS_ENV variable to .bashrc so Unicorn can load the right environment.
deploy@remote $ echo 'export RAILS_ENV=production' >> ~/.bashrc deploy@remote $ source ~/.bashrc
Run cap production deploy:check to make sure all your files and directories are in place. Everything should be successful.
user@local $ cap production deploy:check
Step 10: Configure and restart Nginx.
Back up the default file. I put it in home directory for now.
deploy@remote $ sudo mv /etc/nginx/sites-available/default ~
Create a new nginx default file with these settings. Note that the socket is the same one specified in the Unicorn configuration.
deploy@remote $ sudo vim /etc/nginx/sites-available/default
upstream app { server unix:/tmp/unicorn.deploydemo.sock fail_timeout=0; } server { listen 80; server_name localhost; root /var/www/deploydemo/current/public; try_files $uri/index.html $uri @app; location @app { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_redirect off; proxy_pass http://app; } error_page 500 502 503 504 /500.html; client_max_body_size 4G; keepalive_timeout 10; }
Restart nginx.
deploy@remote $ sudo service nginx restart
Step 11: Deploy the app.
Run cap production deploy. First time will take a while since it has to run bundle and install all dependencies.
user@local $ cap production deploy
Start the unicorn workers.
user@local $ cap production unicorn:start
You should see your simple CRUD app when you go to your IP in the browser.
Conclusion
I hope you have found this guide helpful. If you see any improvements that can be made to this guide or have any questions, feel free to contact me.
Notes
If you’re having issues with symlinks, deleting the “current” symlink and running deploy again might help.
If there’s an error, look at the Capistrano output. There’s usually some information that can help you debug the problem.
Use the cap -T
command to see all Capistrano commands.
Use ps aux | grep unicorn
to check unicorn processes. Capistrano has some commands to stop and restart unicorn workers, but use kill [pid]
to kill the process manually if needed.
Sources
http://blog.mccartie.com/2014/08/28/digital-ocean.html
http://www.gotealeaf.com/blog/deploy-rails-apps-with-capistrano/
http://voiceofchunk.com/2014/06/09/deploying-rails-apps-using-passenger-rbenv-postgresql-and-mina/
http://stackoverflow.com/questions/6282307/execjs-and-could-not-find-a-javascript-runtime
http://sirupsen.com/setting-up-unicorn-with-nginx/
http://theflyingdeveloper.com/server-setup-ubuntu-nginx-unicorn-capistrano-postgres/
http://www.cubicleapps.com/articles/ubuntu-rails-ready-with-nginx-unicorn
http://benjaminknofe.com/blog/2014/03/08/zero-downtime-deployment-with-unicorn-and-capistrano/
http://stackoverflow.com/questions/21692601/capistrano-3-process-failing
http://railscasts.com/episodes/335-deploying-to-a-vps