Chad's Blog

Deploying Rails with Git and Vlad

Posted by Chad Hendry on 22 Feb 2010

Vlad the Deployer’s documentation barely exists. So, I took notes setting up vlad for my personal project, BeerTap.Me, and collected them into this post.

I’m using the following software:

  • Rails 2.3.5
  • Vlad 2.0.0
  • Vlad-Git 2.1.0
  • Ubuntu 9.10 (server)
  • Snow Leopard (dev machine)
  • Apache 2.2
  • Phusion Passenger 2.2.10

Installing Vlad

The vlad gem itself only supports Subversion. There is a separate gem, vlad-git, which contains a plugin for Git. You have to install both gems:

sudo gem install vlad
sudo gem install vlad-git

Next, import vlad’s Rake tasks by adding the following to your Rakefile:

begin
  require 'vlad'
  Vlad.load :scm => :git
rescue LoadError
  # do nothing
end

The server to which you are deploying does not need to have the vlad gem installed, so the begin/rescue allows the exception to be safely swallowed when running Rake on the server. The :scm => :git argument tells vlad to use the git plugin.

Next create a deployment file. By default, vlad looks for this in config/deploy.rb. Mine looks like this:

set :domain, 'beer.chendry.org' # where to ssh
set :deploy_to, '/var/www/BeerTap.Me/' # target directory on server
set :repository, 'git@github.com:chendry/BeerTap.Me' # git repo to clone
set :revision, 'master' # git branch to deploy
set :web_command, 'sudo apache2ctl' # command to start/stop apache

rake vlad:setup

This task needs to be ran only once. It connects to the remote machine and creates the :deploy_to directory and the following directotry structure inside of it:

  • scm/
  • releases/
  • shared/log/
  • shared/pids/
  • shared/system/

You’re probably going to have to tweak the permissions a bit in order to get this to work. I created the deploy directory itself manually, enabled write access for my ssh user, and let vlad create the remaining directories.

rake vlad:update

This is where most of the interesting stuff happens. Not much is documented about what goes on behind the scenes, though, so here’s a brief summary:

First, vlad ssh’s to your remote server’s deploy directory. Then, it blows away everything in scm/repo and replaces it with a fresh clone of the Git repository. Next it tar’s up the branch, creates a release subdirectory named after the current date and time, and extracts the tar into it. Then it deletes the log, public/pids, and tmp/system directories from the freshly deployed Rails site and replaces them with symlinks to shared/log, shared/pids, and shared/system directores, respectively.

Finally, once everything is deployed into the release subdirectory, vlad points the current symlink in the deploy directory to the new release (e.g., /var/www/BeerTap.Me/releases/20100222210521/.) Apache should point to the current directory, as it always points to the current release.

rake vlad:migrate

This task runs rake db:migrate RAILS_ENV=production for the current deployment on the server.

Here is where I ran into my first problem. There is no database.yml in a fresh clone of my rails app. Instead there is a database.yml.example. (I don’t like to keep my production passwords in version control.)

To resolve this, I created a shared/config directory and put my real database.yml there. Then I added a new Rake task to deploy.rb that copies the shared config files into the current release. (Thanks to Helder’s post for the idea:)

namespace :vlad do
  remote_task :copy_config_files, :roles => :app do
    run "cp #{shared_path}/config/* #{current_path}/config/"
  end
end

Now, rake vlaid:migrate runs fine so long as rake vlad:copy_config_files is ran first.

rake vlad:start

NOTE: In vlad 2.0.0, vlad:start has a bug which was reported, months ago, with solution, here and here. Maybe this will be fixed soon, but in the meantime, running the vlad:update and vlad:start tasks as part of the same Rake invocation is the easiest workaround.

The vlad:start task restarts the application by touching tmp/restart.txt and restarts the web server using apache2ctl. I ran into a permission issue here: my ssh user did not have permission to run apache2ctl. So, I changed :web_command to "sudo apache2ctl". However, that too failed with the message:

sudo: no tty present and no askpass program specified
rake aborted!
execution failed with status 1: ssh beer.chendry.org sudo apache2ctl restart

The problem is that there is no terminal and consequently sudo can’t prompt the user for the password. To fix this, I edited the sudoers file (visudo) on the server to allow my ssh user to run apache2ctl without a password:

chendry ALL=(ALL) ALL, (ALL) NOPASSWD: /usr/sbin/apache2ctl

Putting it All Together

Finally, add a rake task to your deploy.rb to do all the steps for a full deploy:

namespace :vlad do
  task :deploy => %w[
    vlad:update
    vlad:copy_config_files
    vlad:migrate
    vlad:start
  ]
end

With that, vlad will do a full deployment to the server: install the latest code, install the configuration files, migrate the database, and (re)start the application/webserver.

geico ringtone loop
stop!