AWS OpsWorks: Lessons Learned

We’ve been using Amazon’s AWS OpsWorks to manage our infrastructure on a recent client project. The website describes OpsWorks as

a DevOps solution for managing applications of any scale or complexity on the AWS cloud.

You can think of it as a middleground between something like Heroku and a manually configured server environment. You can also think of it as Chef-as-a-service. Before reading on, I’d recommend reading this Introduction to AWS OpsWorks, a post I wish had existed when I was first diving into this stuff. With that out of the way, here are a few lessons I had to learn the hard way so hopefully you won’t have to.

You’ll need to learn Chef

The basis of OpsWorks is Chef, and if you want to do anything interesting with your instances, you’re going to have to dive in, fork the OpsWorks cookbooks, and start adding your own recipes. Suppose, like we did, you want to add PDFtk to your servers to merge some documents:

  1. Check the OpsCode Community site for a recipe.
  2. A recipe exists. You lucky dog.
  3. Add the recipe to your fork, push it up, and run it.
  4. It fails. Turns out they renamed the gcj package to gcj-jdk. Fix.
  5. It fails again. The recipe is referencing an old version of PDFtk. Fix.
  6. Great sexy success.

A little bit tedious compared with wget/tar/make, for sure, but once you get it configured properly, you can spin up new servers at will and be confident that they include all the necessary software.

Deploy hooks: learn them, love them

Chef offers a number of deploy callbacks you can use as a stand-in for Capistrano’s before/after hooks. To use them, create a directory in your app called deploy and add files named for the appropriate callbacks (e.g. deploy/before_migrate.rb). For example, here’s how we precompile assets before migration:

rails_env = new_resource.environment["RAILS_ENV"]"Precompiling assets for RAILS_ENV=#{rails_env}...")

execute "rake assets:precompile" do
 cwd release_path
 command "bundle exec rake assets:precompile"
 environment "RAILS_ENV" => rails_env

Layers: roles, but not dedicated roles

AWS documentation describes layers as

how to set up and configure a set of instances and related resources such as volumes and Elastic IP addresses.

The default layer types (“PHP App Server”, “MySQL”) imply that layers distinguish separate components of your infrastructure. While that’s partially true, it’s better to think about layers as the roles your EC2 instances fill. For example, you might have two instances in your “Rails App Server” role, a single, separate instance for your “Resque” role, and one of the two app servers in the “Cron” role, responsible for sending nightly emails.

Altering the Rails environment

If you need to manually execute a custom recipe against your existing instances, the Rails environment is going to be set to “production” no matter what you’ve defined in the application configuration. In order to change this value, add the following to the “Custom Chef JSON” field:

 "deploy": {
 "app_name": {
 "rails_env": "staging"

(Substituting in your own application and environment names.)

We’ve found OpsWorks to be a solid choice for repeatable, maintainable server infrastructure that still offers the root access we all crave. Certainly, it’s slower out of the gate than spinning up a new Heroku app or logging into a VPS and apt-getting it up, but the investment up front leads to a more sustainable system over time. If this sounds at all interesting to you, seriously go check out that introduction post. It’s the post this post wishes it was.

David is Viget's managing development director. From our Durham, NC, office, he builds high-quality, forward-thinking software for PUMA, the World Wildlife Fund,, and many others.

More posts by David