Metal Toad is now a certified AWS Advanced Consulting Partner with expertise in DCX, IoT, mobile, and beyond. Learn more.

Capistrano: Drupal deployments made easy, Part 1

I'm a big fan of having an automated deployment process. It's really the web development analog to the "one step build process", as described in the Joel Test. In the past I have used various shell scripts to perform this task, but I have recently become a convert to Capistrano (or "cap" for short). With Capistrano, uploading your code to the test server is as simple as typing cap deploy. When you're ready to launch in production, it's just cap production deploy.

From capify.org:

Simply put, Capistrano is a tool for automating tasks on one or more remote servers. It executes commands in parallel on all targeted machines, and provides a mechanism for rolling back changes across multiple machines.

In detail, here are the features that got me hooked. There's a lot more that cap can do, and I'll describe some more tricks in part 2 of this post.

  • Atomic deployments with error checking. Cap uses a set of symlinked directories, and the links are updated during the final step. It also won't allow a deployment to keep plowing ahead if an intermediate step fails. This makes your deployment atomic; it will either fail or succeed entirely.
  • Fast rollback. If something does go wrong, getting back to the previous state is as simple as cap deploy:rollback.
  • Parallel execution. If you use multiple servers in a load-balanced environment, cap can make managing them easier.
  • Multistage deployments. "Stages" are different server instances of your code. You may have different servers for development, content entry, and production. With the Multistage extension, cap can share code for common tasks between these stages.

Step-by-step

First, you'll need to install Capistrano, as well as the multistage extensions contained in capistrano-ext:

gem install capistrano
gem install capistrano-ext

Next, you'll need to add a couple of files to your project (the files' contents are listed at the end of this post). I lay out projects like this:

|-- capfile
|-- config
|   |-- deploy
|   |   |-- development.rb
|   |   `-- production.rb
|   `-- deploy.rb
`-- drupal
    `-- index.php

Now edit the paths and project name in config/deploy.rb and config/deploy/development.rb to suit your environment.

You're now ready to run cap deploy:setup. This task only needs to be run once, prior to the first deployment. It will create the necessary directories on your server, which will ultimately look something like the tree below. (Until your first deployment, there may not be anything in the releases directory, nor a link from current). Also notice the setup task created a local_settings.php file. The utility of this is described in my post on stage-specific settings. The web server document root in this example is /var/www/current.

|-- current -> /var/www/releases/20091023202918
|-- releases
|   |-- 20091023191450
|   `-- 20091023202918
`-- shared
    |-- cached-copy
    |-- default
    |   |-- files
    |   `-- local_settings.php
    `-- system

Finally, run cap deploy.

cap deploy will speedily upload a new release of your project, then link it to the current document root if all went well.

Watch for part 2, where I'll cover some extra tasks included in this Capfile, as well as managing multi-site and multi-stage deployments.

config/deploy.rb:

### This file contains project-specific settings ###
 
# The project name.
set :application, "myproject"
 
# List the Drupal multi-site folders.  Use "default" if no multi-sites are installed.
set :domains, ["default"]
 
# Set the repository type and location to deploy from.
set :scm, :subversion
set :repository,  "https://example.com/trunk/drupal"
 
# Use a remote cache to speed things up
set :deploy_via, :remote_cache
 
# Multistage support - see config/deploy/[STAGE].rb for specific configs
set :default_stage, "development"
set :stages, %w(production development)
 
# Generally don't need sudo for this deploy setup
set :use_sudo, false

config/deploy/development.rb

### This file contains stage-specific settings ###
 
# Set the deployment directory on the target hosts.
set :deploy_to, "/var/www/#{application}"
 
# The hostnames to deploy to.
role :web, "devel.example.com"
 
# Specify one of the web servers to use for database backups or updates.
# This server should also be running Drupal.
role :db, "devel.example.com", :primary => true
 
# The username on the target system, if different from your local username
# ssh_options[:user] = 'alice'
 
# The path to drush
set :drush, "cd #{current_path} ; /usr/bin/php /data/lib/php/drush/drush.php"

Capfile

This file does the real work. If you aren't a ruby programmer, don't panic; neither am I (yet). Hopefully, this is general-purpose enough that it will work for you out of the box. Additionally, individual tasks can be overridden in deploy.rb or development.rb – so once you have a Capfile you like the same file can be reused across all your projects.

load 'deploy' if respond_to?(:namespace) # cap2 differentiator
Dir['vendor/plugins/*/recipes/*.rb'].each { |plugin| load(plugin) }
load 'config/deploy.rb'
 
require 'capistrano/ext/multistage'
 
namespace :deploy do
 
  # Overwritten to provide flexibility for people who aren't using Rails.
  desc "Prepares one or more servers for deployment."
  task :setup, :except => { :no_release => true } do
    dirs = [deploy_to, releases_path, shared_path]
    domains.each do |domain|
      dirs += [shared_path + "/#{domain}/files"]
    end
    dirs += %w(system).map { |d| File.join(shared_path, d) }
    run "umask 02 && mkdir -p #{dirs.join(' ')}" 
  end
 
  desc "Create settings.php in shared/config" 
  task :after_setup do
    configuration = <<-EOF
<?php
$db_url = 'mysql://username:password@localhost/databasename';
$db_prefix = '';
EOF
    domains.each do |domain|
      put configuration, "#{deploy_to}/#{shared_dir}/#{domain}/local_settings.php"
    end
  end
 
  desc "link file dirs" 
  task :after_update_code do
    domains.each do |domain|
    # link settings file
      run "ln -nfs #{deploy_to}/#{shared_dir}/#{domain}/local_settings.php #{release_path}/sites/#{domain}/local_settings.php"
      # remove any link or directory that was exported from SCM, and link to remote Drupal filesystem
      run "rm -rf #{release_path}/sites/#{domain}/files"
      run "ln -nfs #{deploy_to}/#{shared_dir}/#{domain}/files #{release_path}/sites/#{domain}/files"
    end
  end
 
  # desc '[internal] Touches up the released code.'
  task :finalize_update, :except => { :no_release => true } do
    run "chmod -R g+w #{release_path}"
  end
 
  desc "Flush the Drupal cache system."
  task :cacheclear, :roles => :db, :only => { :primary => true } do
    domains.each do |domain|
      run "#{drush} --uri=#{domain} cache clear"
    end    
  end
 
  namespace :web do
    desc "Set Drupal maintainance mode to online."
    task :enable do
      domains.each do |domain|
        php = 'variable_set("site_offline", FALSE)'
        run "#{drush} --uri=#{domain} eval '#{php}'"
      end
    end
 
    desc "Set Drupal maintainance mode to off-line."
    task :disable do
      domains.each do |domain|
        php = 'variable_set("site_offline", TRUE)'
        run "#{drush} --uri=#{domain} eval '#{php}'"
      end
    end
  end
 
  after "deploy", "deploy:cacheclear"
  after "deploy", "deploy:cleanup"
 
 
  # Each of the following tasks are Rails specific. They're removed.
  task :migrate do
  end
 
  task :migrations do
  end
 
  task :cold do
  end
 
  task :start do
  end
 
  task :stop do
  end
 
  task :restart do
  end
 
end
 
 
desc "Download a backup of the database(s) from the given stage."
task :download_db, :roles => :db, :only => { :primary => true } do
  domains.each do |domain|
    filename = "#{domain}_#{stage}.sql"
    run "#{drush} --uri=#{domain} sql dump --structure-tables-key=common > ~/#{filename}"
    download("~/#{filename}", "db/#{filename}", :via=> :scp)
  end
end

Comments

We used Capistrano on a large project not too long ago and while we it was mostly a good experience, it was also very frustrating at times. Running gem install or update took forever, Capistrano required specific versions of certain ruby gems and it had pretty much no Wndows support at the time. Since then we've had much better luck with our own bash scripts and haven't looked back.

I can thoroughly recommend my railsless-deploy gem (see github, or gemcutter.org) - it removes all of the railsisms so you don't need to stub out all the rails default methods; should make your output cleaner, and marginally quicker deploys

Lee, Thanks for pointing that out! That sounds like a great tool. That also makes me think that someday a Drupal "personality" for Cap could live on gemcutter, further shrinking or eliminating many of the standard task definitions.

thanks. i havent had time to look into deployment yet, but someone posted your article on irc. now i am curious. thanks for that good starting point.

Add new comment

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>, <cpp>, <java>, <php>. The supported tag styles are: <foo>, [foo].
  • Web page addresses and email addresses turn into links automatically.
  • Lines and paragraphs break automatically.

About the Author

Dylan Tack, Principal Engineer

Dylan Tack doesn’t accept the status quo. Whether he’s diving into a client challenge as a principal engineer at Metal Toad or working on the latest hobby project, his first question is, “What barriers can we remove to make this better?”

As an enterprise software development leader with nearly two decades of experience, Dylan drives technical excellence and ingenuity that enables clients in heavy industry, entertainment and health care to achieve business-changing digital transformations. He works across projects and teams at Metal Toad to architect solutions in machine learning, Internet of Things, security and DevOps.

As a member of Metal Toad’s leadership team, Dylan builds the company’s technology roadmap for the future, guides research and development groups and empowers the technical success of staff by laying the foundation for major projects.

Prior to joining Metal Toad, Dylan was a computational scientist at the University of Iowa, where he worked with supercomputers to apply data science and machine learning solutions to molecular bioresearch.

He enjoys the thrill of skiing and rock climbing, or when a more low-key night is in order, entertaining with his bluegrass banjo.

Ready for transformation?