Deploying new code can be a frightful time. It’s a time when the availability of our applications are most at risk. The procedures we construct to defend against this might also be scary and error-prone. Here at Sparkbox we’ve learned that there are better options, and we’d like to share two alternatives to ease this pain.
The Pain Path
To find where we’re going, it helps to know where we’re coming from. When I speak of painful and buggy deployment processes, I’m primarily thinking about two things: manually copying files and folders over FTP (or similar protocol) or completely hand-crafting deployment scripts. Copying files means manual work, and even if you’re working from a checklist, with so many files to move, you will miss something eventually. Additionally, custom scripts accumulate cruft over time and can be difficult to adapt to other projects. If deployments aren’t repeatable, consistent, and regularly practiced, then any confidence in that process is probably misplaced.
The Solution
Let’s get one thing straight: manual processes and custom scripts must be retired and replaced with a tool that will own the deployment workflow. In many cases, you can extend these tools to include custom build steps, but they are designed to own deployments from beginning to end. Replacement sounds drastic, but help abounds. This guide will help you understand two deployment automation tools in their basic use and how to incorporate custom steps should you need them.
Solution #1: Mina
Mina was first released around June of 2012, making it one of the newest mainstream deployment tools out there. Its creators intend it to be a fast and mostly transparent frontend to Rake, responding to criticism of other more established tools, which are slow and sometimes difficult to customize.
Mina works by starting with a skeleton Bash script and injecting environment details into it at runtime. You can view the skeleton script here. Those details come from a configuration file. Take a look at the following examples.
deploy.rb:
require 'mina/git'
set :repository, 'git://...'
set :env_config, YAML.load_file('./config/env.yml')
set :environment, ENV['on'] || env_config.fetch('default')
desc 'Sets up Mina to deploy to the requested environment'
task :environment do
env_config.fetch(environment).each do |key, value|
set key.to_sym, value.to_s
end
end
task :setup => :environment do
end
desc "Deploys the current version to the server."
task :deploy => :environment do
deploy do
invoke :'git:clone'
invoke :'deploy:link_shared_paths'
to :launch do
end
end
end
env.yml:
staging:
domain: staging.example.com
deploy_to: /var/www/staging_site
branch: master
ssh_options: "-A"
production:
domain: example.com
deploy_to: /var/www/site
branch: production
ssh_options: "-A"
default: staging
This configuration tells Mina where to find our source code, how to login to the server, where the finished build should go, and how to build and copy the application to that location. These basic steps comprise most deployment automation tools, and Mina is no different in this regard.
Our example here is set apart from the official documentation in our handling of multiple environments. At the time of this writing, the Mina documentation does not describe any method for handling multiple environments. Our example defines environments for staging and production in a YAML file. In the Mina-provided environment task, we extract configuration values for the chosen environment from that file and deploy accordingly. This idea could be extended to support multiple hosts in a given environment until Mina has native support for that as well.
What sets Mina apart from other deployment tools—and why it is recommended first in this guide—is the ability to preview the commands it will run prior to execution. Because Mina generates a single Bash script, Mina can produce that script and print it to Standard Out. This makes debugging deployment issues very easy with Mina.
Where Mina perhaps falls short is in highly complex configurations. Mina’s simplest configuration is for one environment with a single host. Going beyond that requires stepping outside of Mina, which could mean simple conditional statements or it could require extending the software itself. Next, we’ll look at a tool that has support for more complex configurations right out of the box.
Solution #2: Capistrano
Basic deployments with Capistrano closely resemble Mina, but we want to pick up Capistrano where its complexities start to pay dividends. Consider the following example, whose capabilities closely mimic the Mina configuration above.
Capfile:
load 'deploy'
load 'config/deploy'
deploy.rb:
set :stages, %w(production staging local)
set :default_stage, 'local'
require 'capistrano/ext/multistage'
set :application, "Multistage Sbx"
set :repository, "git://..."
set :scm, :git
set :deploy_via, :remote_cache
local.rb:
set :deploy_to, '/path/to/local'
server 'localhost', :app, :web, :db, primary: true
set :branch, 'master'
production.rb:
set :deploy_to, '/path/to/production'
server 'example.com', :app, :web, :db, primary: true
set :branch, 'production'
staging.rb:
set :deploy_to, '/path/to/staging'
server 'staging.example.com', :app, :web, :db, primary: true
set :branch, 'production'
Right away you’ll notice there are five files instead of Mina’s two. This is because we’re utilizing an extension to Capistrano called Multistage. This extension has been merged into 2.x in the latest release, so you don’t need anything extra to get these features. The additional files allow you to define environment-specific configuration beyond simply switching between hostnames and git branches. Capistrano Multistage also allows you to define and override tasks based upon the makeup of each environment. Perhaps you use different server software between your staging and production environments and need to define the restart task two different ways. Capistrano will allow you to do that if you follow this pattern. Add the task definition to the environment-specific config file (local.rb, staging.rb, or production.rb in this example).
Just like Mina, Capistrano can be used with many programming languages. We frequently use Capistrano for Ruby and Rails deployments. Support for Rails deployments is added via module, which may be removed from its configuration.
Capistrano is one of the most popular deployment tools available today. The Ruby Toolbox rates it as the most popular amongst its users, but Capistrano isn’t without its warts. Each command that runs as part of your deployment requires its own SSH session, each with its own overhead for initiating and terminating that session. Presumably, you’d be using Capistrano because of your need for custom and environment-specific steps, which would compound the problem. Even if performance isn’t a high concern, be aware that the popular Capistrano 2.x is officially unsupported and future updates and bug fixes are not planned. Capistrano 3.x has been released, and configuration looks similar to this example.
Custom Tasks
If there is a part of your build process that doesn’t fit with these tools, and your organization just can’t live without it, custom tasks can help. Both Mina and Capistrano have the capacity to define and execute custom tasks within a deployment. Defining tasks is similar to both, but how you integrate them into the build process differs. Capistrano uses the before
and after
methods to queue additional tasks to run in a deployment. You call this method with the name of the task you wish to precede or follow and the name of the task you wrote. In Mina, you call the invoke
method with the name of your defined task at the point in your build you want it to happen.
Conclusion
These examples constitute our “happy path” for code deployments to Unix/Linux-based hosts. Though there are many options for accomplishing the goals we set for ourselves here, we landed on Mina and Capistrano for their widespread usage, quality of documentation, and ease of setup. Even more importantly, they have worked for us in real-world applications. Similar stories surely exist for other tools, and we encourage you to seek those out. For instance, these tools do not adequately address deployments within the Windows ecosystem. They may be coerced to work when deploying from Windows but have no support for deploying to Windows systems. In the end, choose a tool and adopt it completely.