As web apps continue evolving and complexity increases, the landscape of web app tools likewise continues to evolve. This provides us developers with new and better ways to build and create cool stuff. Harmony, however, requires configuration, maintenance, and planning.
A current Caxy project consists of an Angular JS frontend with a Symfony2 backend (along with Sonata Admin, User and Media bundles) coupled with a RESTful API between them courtesy of JMSSerializerBundle. When we started compiling for distribution, we were manually running FOSJsRoutingBundle to generate our routes, Composer to update backend dependencies, Bower to update frontend dependencies, Symfony’s app console feature to build specific frontend assets, while Grunt generates the remaining assets and minifies.
In addition to the build complexity, our development process includes a number of stages to which we commit code and subsequently deploy it. Most of our developers work locally and run Ubuntu or Mac OS X. Each dev branches off of a main development branch to work on features, bug fixes, etc. After features are tested and peer reviewed, they are merged into the development branch where they are queued for the QA environment. We host an internal web server with various virtual hosts including environments for alpha, QA, and staging. Each step requires moving code to different environments, clearing cache, setting permissions, dumping assets, and generating routes. Not only is this time-consuming, but it limits the number of people capable of pushing a deployment to a very select few. The high level of complexity introduces myriad opportunity for user error.
Enter Capistrano
To combat all of the above, we recently added Capistrano to the Caxy tool belt. Capistrano helps reduce the amount of manual effort in our development process.
Capistrano is an open-source Ruby application for automating code deployments. I first learned about Capistrano when my co-worker mentioned a program called Capifony which I eventually learned was a spin off of Capistrano specifically for Symfony projects. Capifony looked perfect for what we wanted to do. In the end though, I decided that since Capistrano 3 had just come out and people are writing Symfony-specific gems for Capistrano 3, we should use the new version.
Why Capistrano?
Obviously, there are quite a few configuration management tools out there including, but not limited to Jenkins, Puppet, Chef, and Ansible. While we could simply build a bunch of bash scripts specific to our project, Capistrano is a cohesive tool - a better solution long term. We used to use Jenkins to deploy code, but it was more difficult to configure and didn't exactly seem to fit our needs. After researching several alternatives, we decided to go with Capistrano as it seemed that these other tools were being used for more robust, larger scale apps that were being deployed across distributed architectures and cloud clusters. We were looking for something that was designed for code deployment and not infrastructure management.
Barriers to Entry
Lack of documentation is probably the largest barrier to entry with Capistrano. Because Capistrano 3 is still fairly new, there is not a lot of great documentation out there. It really took quite a bit of testing and trying things to get it working. From the time I started looking into it to the time I ran my first successful deployment it was probably a week or so with off and on tinkering.
Setting Up
To get started, I followed along with installation instructions and short tutorials on the official website. These instructions may not be for the greenest of novices; if you’ve never set up things like SSH keys or SSH forwarding you might have some trouble, but this stuff is worth learning, and not only for Capistrano.
After getting the basics set up so my various environments could access the remote git repo from my local machine, it was time to dig into the fun part.
When I first started looking at how Capistrano was configured, I thought I was doomed as I knew nothing about Ruby. While it would have been helpful to know Ruby, I quickly found Capistrano gems for almost every task I needed to accomplish (thank you community).
After running:
$ cap install
I placed the following lines in my Gemfile:
source "https://rubygems.org"
gem 'capistrano', '~> 3.1'
gem 'capistrano-symfony', '~> 0.3', :github => 'capistrano/symfony'
gem 'capistrano-bower'
gem 'capistrano-grunt', github: 'roots/capistrano-grunt'
I then ran:
$ bundle install
After installing the gems, it was on to configuration. The main deploy.rb file holds the config that stays consistent across environments. The lines of interest here are:
set :linked_files, %w{app/config/parameters.yml}
set :linked_dirs, %w{node_modules log vendor/bundle web/uploads}
# Run Grunt
set :grunt_file, -> { release_path.join('gruntfile.js') }
set :grunt_flags, 'deploy'
before 'deploy:updated', 'grunt'
# Set Symfony permissions
set :file_permissions_paths, ["app/logs", "app/cache", "app/var", "web/uploads"]
set :file_permissions_users, ["www-data"]
before "deploy:updated", "deploy:set_permissions:acl"
Finally, in our environment-specific configuration, we tell Capistrano where to deploy the code and what else to do:
set :stages, ["staging"]
set :branch, "stage"
# Install composer
SSHKit.config.command_map[:composer] = "php #{shared_path.join("composer.phar")}"
namespace :deploy do
before :starting, 'composer:install_executable'
end
Capistrano Structure
When Capistrano deploys to a release, a complete copy of the code is deployed *each* time. Capistrano create the following 1 level above your webroot:
/releases
Folder containing all deployed releases. Capistrano creates new directories for each release. Release directories are time stamped.
/current
Symlink to the newest release inside /releases
/shared
This folder contains all files & directories that are common among all of the releases. If you have a big directory of images that you don’t want to deploy each time you can simply place that directory in shared and tell Capistrano to share it. You’ll notice above, that we have shared our app/config/parameters.yml file (as this file is not stored in our Git repo) and our node_modules directory for stuff like Grunt and Bower.
Performance
Today, Capistrano is handling the following tasks for us:
Checks out specific branches to QA, staging and production environments
Installs the Composer binary for each instance
Sets Symfony 2 permissions using ACLs
Runs Bower
Runs Grunt
Tracks the deployment in our cloud based code repo
So far, I have not had any trouble with Capistrano 3 itself. I have run into a few problems with 3rd party gems, but overall it’s been great. While our experience with Capistrano is limited, thus far it has exceeded my expectations in automating tasks and performing deployments and we will absolutely be using Capistrano again in any future projects that calls for it.
While most clients don’t really get nerd excited about Capistrano stuff like we do… they should! In the end, tools like this greatly improve our process and automation and in the end, result in a better product.
Additional Benefits
Tying into 3rd Party Systems
Our repository provider, Codebase, has a section in projects to track deployments for particular repositories. They created a ruby gem that worked with Capistrano to track details about deployments such as dates and times, commits added, and servers/environments involved.
There are lots of helpful Capistrano gems like this and tying into other systems is usually as easy as just adding a line or two to your Gemfile and Capfile.
Rollback
If a release goes wrong, you can rollback to a stable version simply by pointing /current at a stable version in /releases