Deploy Rails 3 Unicorn applications using Capistrano on a shared web host non-root

In my struggle to have complete Rails3 environments setup in shared web hosts, the deployment step was missing. Lately I managed to deploy Rails3 Unicorn applications using Capistrano on a shared web host, as a non-root user with very limited privileges (jailshell). So I decided to share this with you.

  1. Setup a Git repository to deploy source files
  2. Install Capistrano
  3. Adapt Unicorn configuration
  4. Deploy your application
  5. Useful links

For information, here are the versions used:

  • On my development host (from where I issue cap commands) [Windows 7 64bits]:
    • Ruby: 1.9.3p194
    • RubyGems: 1.8.24
    • Bundler: 1.2.4
    • Rails: 3.2.12
    • Capistrano: 2.14.2
    • capistrano-unicorn: master branch from GitHub repository
  • On my production environment [Linux i686]:
    • Ruby: 1.8.7p370
    • RubyGems: 1.6.2
    • Bundler: 1.2.4
    • Unicorn: 4.6.0

Here are some prerequisites for this tutorial:

  • The production environment is accessed using SSH.
  • The production host provides a development environment (cc, ld, make).
  • We will push our code to the production host using Git.
  • If git is not installed on your production host, you can download the sources here as a Zip file, then compile and install it in your production user home space. No need to be root.

For the sake of this tutorial, the production host is called “mysite.com”, the SSH connection is made on port “1234”, the user running everything is named “myuser”. The Rails3 application is called “myapp”, it is present on development host in “/path/to/myapp”, and will be pushed on our production host in “/home/myuser/myapp.git” and deployed in “/home/myuser/myapp_deployed” directories. Prompts made on the production host begin with “[@production”, whereas the ones on the development (local) host begin with “[@development”.

Here are all the steps:

Setup a Git repository to deploy source files

First thing is to setup a git repository on the remote production host. This repository will then be accessed using SSH to push files, then Capistrano will pull from it to deploy your Rails3 application.

  1. Initialize a bare Git repository on production host:
    [@production:/home/myuser]> mkdir myapp.git
    [@production:/home/myuser]> cd myapp.git
    [@production:/home/myuser]> git init --bare
    

    This repository will then be accessible using URL ssh://myuser@mysite.com:1234/home/myuser/myapp.git

  2. Setup your local Git repository to push to this remote one.:
    [@development:/path/to/myapp]> git remote add production ssh://myuser@mysite.com:1234/home/myuser/myapp.git
    

Install Capistrano

  1. Add capistrano and capistrano-unicorn gems in your Gemfile
  2. . For the time of this writing, capistrano-unicorn gem has to be fetch from its GitHub repository, as released version (0.1.6) is buggy.

    gem 'jquery-rails'
    gem 'prototype-rails'
    
    group :development do
      gem 'meta_request', '0.2.0'
      gem 'sqlite3'
    
      # Capistrano stuff
      gem 'capistrano'
      gem 'capistrano-unicorn', :git => 'https://github.com/sosedoff/capistrano-unicorn.git', :branch => 'master', :require => false 
    end
    
    group :production do
      gem 'unicorn'
      gem 'mysql2'
    

    Putting those in the :development group is enough as production environment won’t need them.

  3. Run bundle install to install those new gems.
  4. Run capify . to capify you Rails3 application. This will create 2 files: Capfile and config/deploy.rb.
  5. Edit the generated Capfile file to uncomment the line related to assets pipeline. This will define additional code for Capistrano to precompile assets when deploying in production.
    load 'deploy'
    # Uncomment if you are using Rails' asset pipeline
    load 'deploy/assets'
    load 'config/deploy' # remove this line to skip loading any of the default tasks
    
  6. If your development and production platforms are different (MacOS/Linux/Windows): edit your .gitignore file and make sure that Gemfile.lock is part of it.. The reason is simple: Bundler is not meant to be cross-platform (see this bug report). Some of the gems you bundle from your development host won’t be the same on your production host. Therefore we don’t want to deploy Gemfile.lock on our production platform: it will be generated with a nice bundle install made by Capistrano.
    # Ignore private files
    private
    
    # No Gemfile.lock as dev and prod platforms are not the same
    Gemfile.lock
    
  7. Edit file config/deploy.rb, and adapt it to your needs. Here are the considerations to take into account:
    • Add require 'bundler/capistrano': It will add tasks needed to issue Bundler commands.
    • Set your application name.
      set :application, 'My application'
      
    • Set your SSH configuration. This includes your SSH username, SSH connection port, directive to not use sudo, and terminal type.
      set :user, 'myuser'
      ssh_options[:port] = 1234
      set :use_sudo, false
      default_run_options[:pty] = true
      
    • Setup environment variables: Beware that if you tuned some environment variables in your SSH sessions to use locally installed gems or binaries, SSH connections made by Capistrano might not have them. You can set them this way.
      set :default_environment, { 
        'PATH' => '/home/myuser/bin:/home/myuser/ruby/gems/bin:/usr/local/bin:/bin:/usr/bin',
        'RUBYOPT' => '-I/home/myuser/rubygems/inst/lib',
        'GEM_PATH' => '/home/myuser/ruby/gems:/usr/lib/ruby/gems/1.8',
        'GEM_HOME' => '/home/myuser/ruby/gems'
      }
      
    • Setup the Git repository location. Here we specify the production repository URL, accessed from the local development environment using :local_repository and from the production environment using :repository.
      # Source repository taken for deployments
      set :local_repository,  'ssh://myuser@mysite.com:1234/home/myuser/myapp.git'
      set :repository, '/home/myuser/myapp.git'
      set :scm, :git # You can set :scm explicitly or Capistrano will make an intelligent guess based on known version control directory names
      # Or: `accurev`, `bzr`, `cvs`, `darcs`, `git`, `mercurial`, `perforce`, `subversion` or `none`
      
    • If your development and production platforms are different (MacOS/Linux/Windows): Set bundler flags manually to not include default --deployment. By default, Capistrano will bundle install using the --deployment flag, expecting a Gemfile.lock to be present. As we want it to be regenerated, we have to force the bundler flags without this one.
      set :bundle_flags, ''
      
    • Set the deployment directory:
      set :deploy_to, '/home/myuser/myapp_deployed'
      
    • Set your production server for all the roles.
      role :web, 'mysite.com'                          # Your HTTP server, Apache/etc
      role :app, 'mysite.com'                          # This may be the same as your `Web` server
      role :db,  'mysite.com', :primary => true # This is where Rails migrations will run
      
    • At the end of the file, add the Unicorn specific tasks. It is important to require the ‘capistrano-unicorn’ gem after previous directives, as it can overwrite some variables you have set previously 🙁
      # Unicorn tasks
      require 'capistrano-unicorn'
      after 'deploy:restart', 'unicorn:reload' # app IS NOT preloaded
      after 'deploy:restart', 'unicorn:restart'  # app preloaded
      

    Here is an example of a complete deploy.rb file:

    require 'bundler/capistrano'
    
    set :application, 'My application'
    
    # SSH configuration
    set :user, 'myuser'
    set :use_sudo, false
    ssh_options[:port] = 1234
    default_run_options[:pty] = true
    set :default_environment, { 
      'PATH' => '/home/myuser/bin:/home/myuser/ruby/gems/bin:/usr/local/bin:/bin:/usr/bin',
      'RUBYOPT' => '-I/home/myuser/rubygems/inst/lib',
      'GEM_PATH' => '/home/myuser/ruby/gems:/usr/lib/ruby/gems/1.8',
      'GEM_HOME' => '/home/myuser/ruby/gems'
    }
    
    # Source repository taken for deployments
    set :local_repository,  'ssh://myuser@mysite.com:1234/home/myuser/myapp.git'
    set :repository, '/home/myuser/myapp.git'
    set :scm, :git # You can set :scm explicitly or Capistrano will make an intelligent guess based on known version control directory names
    # Or: `accurev`, `bzr`, `cvs`, `darcs`, `git`, `mercurial`, `perforce`, `subversion` or `none`
    set :bundle_flags, ''
    
    # Destination of deployments
    set :deploy_to, '/home/myuser/myapp_deployed'
    # set :deploy_via, :copy
    
    role :web, 'mysite.com'                          # Your HTTP server, Apache/etc
    role :app, 'mysite.com'                          # This may be the same as your `Web` server
    role :db,  'mysite.com', :primary => true # This is where Rails migrations will run
    
    # if you want to clean up old releases on each deploy uncomment this:
    after 'deploy:restart', 'deploy:cleanup'
    
    # if you're still using the script/reaper helper you will need
    # these http://github.com/rails/irs_process_scripts
    
    # If you are using Passenger mod_rails uncomment this:
    # namespace :deploy do
    #   task :start do ; end
    #   task :stop do ; end
    #   task :restart, :roles => :app, :except => { :no_release => true } do
    #     run "#{try_sudo} touch #{File.join(current_path,'tmp','restart.txt')}"
    #   end
    # end
    
    # Unicorn tasks
    require 'capistrano-unicorn'
    after 'deploy:restart', 'unicorn:reload' # app IS NOT preloaded
    after 'deploy:restart', 'unicorn:restart'  # app preloaded
    
    

Adapt Unicorn configuration

With our application deployed using Capistrano, the deployed application path on our production host is “/home/myuser/myapp_deployed/current”. Capistrano also requires the unicorn.rb configuration file to be by default in “config/unicorn/production.rb”. So here are a few changes to do.

  1. Move your unicorn configuration file:
    [@development:/path/to/myapp]> mkdir config/unicorn
    [@development:/path/to/myapp]> mv config/unicorn.rb config/unicorn/production.rb
    
  2. Adapt unicorn configuration with the new deployed path. This has to be adapted to your specific configuration needs.
    # Production specific settings
    if env == "production"
      # Help ensure your application will always spawn in the symlinked
      # "current" directory that Capistrano sets up.
      working_directory '/home/myuser/myapp_deployed/current'
     
      # feel free to point this anywhere accessible on the filesystem
      user 'myuser', 'mygroup'
      shared_path = '/home/myuser/myapp_deployed/current'
     
      stderr_path '/home/myuser/myapp_deployed/current/log/unicorn.stderr.log'
      stdout_path '/home/myuser/myapp_deployed/current/log/unicorn.stdout.log'
    end
    

Deploy your application

In this section, commands run should deploy your application. For each command, check the output in detail and do not issue next commands if you encounter errors with previous ones. In case of errors, investigation and correction is needed before continuing.

  1. Commit modified files in your local Git repository.
    [@development:/path/to/myapp]> git add -A
    [@development:/path/to/myapp]> git commit -m"Added Capistrano support for mysite.com"
    
  2. Push your repository to your production host
    [@development:/path/to/myapp]> git push production master
    
  3. Setup Capistrano for its first time use. This step is not needed for subsequent deploys.
    [@development:/path/to/myapp]> cap deploy:setup
    [@development:/path/to/myapp]> cap deploy:check
    

    If you get the error cannot load such file -- capistrano-unicorn (LoadError), using the bundle exec command in front of all your cap commands:

    [@development:/path/to/myapp]> bundle exec cap deploy:setup
    [@development:/path/to/myapp]> bundle exec cap deploy:check
    
  4. Deploy using Capistrano:
    [@development:/path/to/myapp]> cap deploy
    

    If you have database migrations to be run, use the following instead:

    [@development:/path/to/myapp]> cap deploy:migrations
    

And that’s it! You should have your running Unicorn server on your production host, installed in /home/myuser/myapp_deployed/current.

Subsequent deploys can be performed, following the same commands from your development host only:

[@development:/path/to/myapp]> git add -A
[@development:/path/to/myapp]> git commit -m"New commit"
[@development:/path/to/myapp]> git push production master
[@development:/path/to/myapp]> bundle exec cap deploy:migrations

Useful links

In case of problems, here are some links that helped me a lot in setting this up:

About Muriel Salvan

I am a freelance project manager and polyglot developer, expert in Ruby and Rails. I created X-Aeon Solutions and rivierarb Ruby meetups. I also give trainings and conferences on technical topics. My core development principles: Plugins-oriented architectures, simple components, Open Source power, clever automation, constant technology watch, quality and optimized code. My experience includes big and small companies. I embrace agile methodologies and test driven development, without giving up on planning and risks containment methods as well. I love Open Source and became a big advocate.
Git, Howto, Ruby on Rails, Web development , , , , , , , , , , ,

Leave a Reply

Your email address will not be published.