Legacy Deploy with Capistrano

2 minute read

So, you’re on a rescue project with some legacy code (and by “legacy” I almost always mean PHP). The old developer probably just FTP’ed changes up to the server. You could do the same, but frankly that fills me with dread. On a good day I don’t like deploys. What if something goes wrong?!?!?! To help me to relax, I use good tools that let me trust my deploy and allow me to rollback if something doesn’t work.

In a Rails project you’d get that security from Capistrano. Turns out that’s the right tool for you legacy project as well. Capistrano 2 had a few, easily addressed, Rails-centric tasks. However, Capistrano 3 is framework agnostic by default. (Am I implying PHP is a framework? No, I am not.) First, you want the code under some for of software revision control, specifically Git. If your project has nothing, put it under Git:

cd the-project
git init
git add .
git commit -m 'Initial commit'

If it’s under SVN, convert the repository to Git, here’s a lovely tutorial. Something else? Convert it to Git.

(Actually, Capistrano 3 does have some support for SVN and other SCMs though not as front and center as Capistrano 2 did. You’re own your own for the configuration details.)

In the root of your project create a Gemfile:

source 'https://rubygems.org'
group :development do
  gem 'capistrano'
end

Then bundle install. Once the bundle finishes, run cap install. This will set up the Capfile, cap tasks, and config files. For a Rails project you might need to tweak the Capfile to enable support for bundler, assets, etc. However, for our legacy deploy we need none of that.

What we do need to do is edit the configs, config/deploy.rb, config/deploy/production.rb, config/deploy/staging.rb.

In deploy.rb you’ll want to set the application name, the URL of your repo, and the deploy directory:

set :application, 'legacy_app'
set :repo_url, 'git@github.com:me/legacy_app.git'

All that staging.rb and/or production.rb need are the remote host and user to deploy to. You can just use the app role:

server 'example.com', user: 'me', roles: %w{app}

However, if you are deploying to the same server for staging and production, also override the deploy path (which defaults to “/var/www/application”) for at least one stage lest one clobber the other:

set :deploy_to, "/var/www/#{fetch(:application)}_staging"

After that it’s as simple as cap staging deploy or cap production deploy!

Just as it would with Rails, Capistrano will deploy in to “current” in the deploy directory. You’ll likely need to tweak your web server config or symlink the current document root to the current directory. Definitely worth it, as this is what gives you cap production deploy:rollback.

Also as with Rails, you can add additional tasks that run a various points in the deploy. For example, if you needed to clear the Varnish cache you could put the following in lib/capistrano/tasks/varnish.rake

namespace :varnish do
  desc 'Clear Varnish cache'
  task :clear_cache do
    on roles(:app) do
      execute "varnishadm 'ban.url .'"
    end
  end
end

and add:

namespace :deploy do
  after :published, 'varnish:clear_cache'
end

to your config/deploy.rb

There’s a good intro to using hooks to run your own tasks here. I clear cache the after the new code has been published, but there are plenty of other points in the deploy where tasks can run. A full list of what fires when can be found in http://capistranorb.com/documentation/getting-started/flow/

With a setup like this you can quickly deploy a new version of whatever mess you inherited and, more importantly, roll it back with a single command. I’m feeling more relaxed already. Sorry about the PHP.

Comments