Glad You're Ready. Let's Get Started!

Let us know how we can contact you.

Thank you!

We'll respond shortly.

The simplest deployment that could possibly work

I’ve been working on a basic chef solo based rails deployment. I started with Building an AMI, then bootstrapping to get the instance running chef with capistrano. Since part two left us with chef running on a box with recipes, it’s time to get a rails app running and serve some requests.

For the moment, I’ve created an application.rb recipe, which is the only thing in the soloistrc. It declares dependencies:

include_recipe "joy_of_cooking::daemontools"
include_recipe "joy_of_cooking::mysql"

and then gets on with dealing with the app server.

‘What is daemontools‘? I hear you cry out. it’s the best way I’ve found to ensure processes are running as you expect. If you aren’t using daemontools (and you probably aren’t) it’s worth looking at seriously. Daemontools takes the pids out of process management. There are no pid files to get out of sync with processes, and there’s no polling to see if something is up. Daemontools know if something exists because it is a child process, and when it exits it returns control to the supervise process. Deamontools is worth wrapping your head around, but for now, all you need to know is that you give it a script and it runs the script in an infinite loop. I stole the install procedure from Michael Sofaer’s Hellspawn, so you’ll have to excuse the fact that it looks like ruby instead of chef.

ruby_block "install daemontools" do
  block do
    directory = "/package/admin"
    repo = "git://"
    dir_name = "daemontools-0.76"
    FileUtils.mkdir_p directory
    system("cd #{directory} && git clone #{repo} #{dir_name}")
    system("cd #{File.join(directory, dir_name)} && ./package/install")
  not_if "ls /command/svscanboot"

Once daemontools is installed, we move on to installing mysql. A database isn’t necessary for an app, but it’s necessary for doing anything interesting. I made a philisophical decision to compile mysql from source. I tend to view compiling from source as a continuum – you usually would compile your own app – you usually wouldn’t compile your own kernel. The DB is closely related enough to your app that it’s nice to have exact documentation about what you’re running. It’ll also lower the barrier to entry to trying out Percona or Drizzle.

The mysql recipe is a little too long to inline here, but you can and should check it out on github. The recipe starts by installing some dependencies (by all means, leverage your package management system here) create a mysql user and download/cmake/make install. Add a deamontools run script, and up it comes. Set up the users and we’re all set. The most interesting part of the recipe to me is the block which waits for mysql to start up:

ruby_block "wait for mysql to come up" do
  block do
    Timeout::timeout(60) do
      until system("ls /tmp/mysql.sock")
        sleep 1

Usually I’d just throw in a sleep, but I was ashamed to share that with the world. This is a technique I’ll carry forward, and would love to see it make its way into chef so those less prone to dropping into ruby could make use of it. (Also, if there’s a better way or something that’s already in Chef that I haven’t come across, I’m all ears)

Once mysql is up and running, we do the git clone/bundle/db:create/db:migrate steps rails developers have come to know and love, then write out a deamontools run script and use svc to make sure the unicorn restarts on every deploy. I’m not a fan of the cap style cached copy/magical rollback that the chef deploy resource reimplements, so for now I’m rolling my own.

Once the app is ready to be started, write out a daemontools run script:

file "/service/unicorn/run" do
  content %{#!/bin/bash
cd /var/staging/foo/src
export RAILS_ENV=staging
source /home/mkocher/.rvm/scripts/rvm
rvm use ruby-1.8.7-p299@captest
exec /command/setuidgid mkocher rackup -p 3000
  mode "0755"

And daemontools starts up the app. The run script is pretty easy to follow – set up RVM, chose our ruby, and exec unicorn.

Hitting the app on 3000 reveals a rails app in all its scaffolded glory:

app screenshot

There’s more work still do to. There’s a repetition that needs to be refactored out. Capistrano, Chef and Rails all need to share some knowlege about the world. Cap and chef need to know the directory we’re deploying the thing to, chef and rails need to know what the database configuration is, and so on. Cap has settings, Chef has nodes and Rails has Configure blocks. I’m leaning towards reinventing the weel again very simply, but I’m open to suggestions. Other todo items are putting nginx in front of the app server, and moving to a dedicated database server.

I’m not sure which direction this will go next, but I hope by now you’re convinced that chef recipes aren’t magic – they’re just a thin ruby wrapping around the things you do to configure a box – execute some commands and edit some files.

All the recipes mentioned are available on github and MIT licensed, please steal them and make them better.


  • You’ll need to add whatever ports you’d like open to your EC2 security group.
  • Having mysql on a AMI backed instance with the data on the local disk is the opposite of production ready.
  • Sometimes things in the world changes – either mirror files yourself, or expect the occasional mirror to disappear failing a chef run. Wayne Seguin will also occasionally change the install script location for RVM. Chef recipes are living things which must be tended to occasionally.

  1. Tienshiao Ma says:

    I’m doing research on budgetting and building out a Rails infrastructure on EC2 and have found your blog posts useful.

    What size instances have you found work best for your application?

  2. Matthew Kocher says:

    I try and stick with 64bit architecture, simply because I think it’s not worth the overhead of supporting two architectures. This means practically you’re starting with m1.large instances, which currently run $250 a month.

    From there you’ve got to do load testing and decide what instances make sense. At a minimum, you’ll want at least one extra app server than is necessary for handling your load, so you’re OK if one disappears and you need to rebuild it.

  3. Do I see it right when your script does

    source /home/mkocher/.rvm/scripts/rvm

    that is executed with root privileges? You switch to the “mkocher” user only the last instruction,

    exec /command/setuidgid mkocher rackup -p 3000

    Not 100% secure.


Post a Comment

Your Information (Name required. Email address will not be displayed with comment.)

* Copy This Password *

* Type Or Paste Password Here *