Ruby on Rails deployment

From Smith family
Jump to: navigation, search

Various different ways to deploy a Ruby on Rails application. Assumes the server is set up and the databases are created.

Note that config/database.yml, config/initializers/secret_token.rb, and config/deploy.rb aren't in the the Git repo.

One-time setup: create the deploy user

  • On the server where the production systems will be deployed, create a new deploy user and lock the account
root@server:~# adduser deploy
root@server:~# passwd -l deploy

Add the deploy user to the list of users who can SSH into the server. Add deploy to the AllowUsers line in /etc/ssh/sshd_config

AllowUsers user1 user2 user3 git deploy
the restart sshd:
root@server:~# service ssh restart
  • Import the public keys from all users that need to trigger a deployment.
root@server:~# sudo -u deploy -i
deploy@server:~$ scp user@desktop:.ssh/
deploy@server:~$ cat >> .ssh/authorized_keys
deploy@server:~$ rm
user@desktop should now be able to log into deploy@server without a password.
  • Generate a public key for deploy@server:
deploy@server:~$ ssh-keygen -t rsa
  • Go back to wherever the Gitolite admin repository is. Add the deploy user's public key to the gitolite admin repo.
user@desktop:/gitolite-admin$ scp deploy@server:.ssh/ keydir/
  • In conf/gitolite.conf, add deploy as a read-only user for the repos you want them to deploy, e.g.
repo sample
    RW+     =   user
    R       =   daemon deploy
    desc = Sample Rails application
  • Commit and push the changes.
user@desktop:/gitolite-admin$ git add .
user@desktop:/gitolite-admin$ git commit -a -m "Added deploy user"
user@desktop:/gitolite-admin$ git push
  • Back as the deploy user, check that you can clone a repo. (This also allows you to pass SSH's host authenticity test.)
deploy@ogedei:~$ git clone git@git.domain.tld:sample.git
  • Make sure the deploy user can use RVM. Add the following to /home/deploy/.bashrc:
if [ -f /etc/profile.d/ ]; then
  source /etc/profile.d/
  • Check deploy is using the correct Ruby version
deploy@server:~$ source /etc/profile.d/
deploy@server:~$ rvm list

rvm rubies

=* ruby-2.1.0 [ x86_64 ]

# => - current
# =* - current && default
#  * - default

deploy@server:~$ which ruby

Per-project setup

These are the steps needed to set up a project for the initial deployment.

Capify the project

  • On the development machine, initialise Capistrano:
user@desktop:~/project$ cap install STAGES=production
(STAGES=production omits the default "staging" deploy target.)
  • Edit project/config/deploy/production.rb
role :app, %w{deploy@server.domain.tld}
role :web, %w{deploy@server.domain.tld}
role :db,  %w{deploy@server.domain.tld}, primary: true
and ensure the "exteneded server syntax" line further down the file is commented out.
  • Edit project/config/deploy.rb
set :application, 'project'
set :repo_url, 'git@git.domain.tld:project.git'
set :branch, 'master'
set :default_stage, 'production'

set :deploy_to, '/var/www/project.domain.tld'
set :scm, :git

set :format, :pretty
# set :log_level, :debug
# set :pty, true

set :linked_files, %w{config/database.yml config/initializers/secret_token.rb}
set :linked_dirs, %w{bin log tmp/pids tmp/cache tmp/sockets vendor/bundle public/system}

set :default_env, { path: "/opt/ruby/bin:$PATH" }
set :keep_releases, 5

namespace :deploy do

  desc 'Restart application'
  task :restart do
    on roles(:app), in: :sequence, wait: 5 do
      execute :touch, release_path.join('tmp/restart.txt')

  after :restart, :clear_cache do
    on roles(:web), in: :groups, limit: 3, wait: 10 do
      # Here we can do anything such as:
      # within release_path do
      #   execute :rake, 'cache:clear'
      # end

  after :finishing, 'deploy:cleanup'

  • Edit Capfile
require 'capistrano/rvm'
require 'capistrano/bundler'
require 'capistrano/rails/assets'
require 'capistrano/rails/migrations'

Create the Apache2 virtual host

  • Find out the correct path to use for the ruby you want (assumed to to be Ruby 2.1.0 here):
root@ogedei:~# rvm use 2.1.0
root@ogedei:~# passenger-config --ruby-command
passenger-config was invoked through the following Ruby interpreter:
  Command: /usr/local/rvm/gems/ruby-2.1.0/wrappers/ruby
  Version: ruby 2.1.0p0 (2013-12-25 revision 44422) [x86_64-linux]
  To use in Apache: PassengerRuby /usr/local/rvm/gems/ruby-2.1.0/wrappers/ruby
  To use in Nginx : passenger_ruby /usr/local/rvm/gems/ruby-2.1.0/wrappers/ruby
  To use with Standalone: /usr/local/rvm/gems/ruby-2.1.0/wrappers/ruby /usr/bin/passenger start
and place the PassengerRuby path in the Apache2 config below.
  • Create the site virtual host file for Apache2. Create the file /etc/apache2/site-available/project.domain.tld:
VirtualHost *:80>
    ServerName project.domain.tld
    DocumentRoot /var/www/project.domain.tld/current/public
    PassengerRuby /usr/local/rvm/gems/ruby-2.1.0/wrappers/ruby

    <Directory /var/www/project.domain.tld/current/public/>
        Allow from all
        Options -MultiViews

    LogLevel warn
    ErrorLog /var/log/apache2/error.log
    CustomLog /var/log/apache2/project.access.log combined

    ServerSignature Off
  • Create the directories and give them the correct permissions: deploy need to write to them, www-data needs to read them:
root@ogedei:~# mkdir -p /var/www/project.domain.tld/public                                                                                                   
root@ogedei:~# mkdir -p /var/www/project.domain.tld/config
root@ogedei:~# chown -R deploy:www-data /var/www/project.domain.tld
root@ogedei:~# ls -lah /var/www/project.domain.tld/
total 16
drwxr-xr-x  4 deploy www-data 4.0K Jan 10 20:35 .
drwxr-xr-x 13 root   root     4.0K Jan 10 20:35 ..
drwxr-xr-x  2 deploy www-data 4.0K Jan 10 20:35 config
drwxr-xr-x  2 deploy www-data 4.0K Jan 10 20:35 public

Create the production database

  • On the server, create the (empty) database
root@server:~# sudo -u postgres psql postgres
could not change directory to "/root"
psql (9.1.11)
Type "help" for help.

postgres=# create role projectuser with createdb login password 'projectpassword';
postgres=# create database projectdb owner=projectuser;
postgres=# \q
  • Add the production database password to config/database.yml.

Make the first deployment

  • Run the deployment check:
user@desktop:~project$ bundle exec cap production deploy:check
which will create additional directories on the server.
  • Manually copy across the conig/database.yml file to the server:
user@desktop:~project$ scp config/database.yml deploy@server:/var/www/project.domain.tld/shared/config/
  • Rerun the deployment check:
user@desktop:~project$ bundle exec cap production deploy:check
and you should get a nice green list of successes.
  • Deploy the project!
user@desktop:~project$ bundle exec cap production deploy

Subsequent deployments

They're as simple as the first deployment:

user@desktop:~project$ bundle exec cap production deploy

See also