Deploying Rails with Git and Vlad
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.