Embed just a cookbook in your Ruby project, not a whole kitchenPublished 2014-01-13
To embed a Chef cookbook in your Ruby web project, run
berks cookbook cookbook from your project's top-level directory. Setup Vagrant so everyone can easily deploy. But for production, point to the cookbook from a
Berkfile in a kitchen outside your project, with the
If you're a Ruby web developer and you handle deployment yourself (instead of having a sysadmin or Heroku do it for you), you can imagine the benefit of automating deployment. This post assumes that you've decided on Chef because of its popularity with the Ruby community, and now you're planning to write a Chef cookbook and distribute it with your Ruby project, so that future maintainers can easily deploy with your cookbook.
Unfortunately, the Chef documentation assumes you have a single "chef-repo" in version control, which contains cookbooks for all your projects, as well as info you wouldn't want to make public, such as passwords. So it's not obvious how your chef-repo and your project's repo should relate to each other. Given the enormous flexibility of Chef and Ruby and community tool contributions, there must be a way to supply a cookbook along with your project -- but how?
(For the rest of this post, I'll assume you're using chef-solo with a kitchen directory created by
knife solo init, instead of chef-server with a chef-repo, but the two situations are similar.)
Solution #1 (bad): embed your project inside your cookbook
Recommended by Mischa Taylor's post, but it's probably intended as a toy example.
Start with an empty Berks cookbook:
berks cookbook myface.
Later, add your source code to
templates/default/index.php.erb for a PHP app) and refer to it with
template from your recipe.
It's straightforward to see how your Chef recipe finds your project's source code files; they're right under
Requires turning your project into a Chef cookbook, burying its source files under
files. A developer may not be able to run your app without deploying it with Chef.
Solution #2 (okay): store your project's cookbook in a totally separate repo
Recommended by Jamie Winsor's post (we don't see where the dropbox.com artifacts come from, so I assume it's from another repo)
Approach Create a separate Git repository for your Chef cookbook. For example, if your project were named
ort you'd have a separate repo named
ort-cookbook. You and every developer will then use librarian-chef or Berksfile to refer to that cookbook from their kitchen.
Advantage This option is the easiest to understand, and corresponds best to the Berkshelf and librarian-chef docs.
Disadvantage Requires two Git repos. An outside developer could be completely unaware of the second Git repo or neglect to keep the two in sync.
Solution #3 (better): embed an entire kitchen inside your project
kitchen solo init to create a "my_kitchen" directory inside your project.
Now "my_kitchen" will contain a
cookbooks and a
According to the Knife solo docs, your custom cookbook(s) should go in
site_cookbooks since the
cookbooks dir will be managed by Berkshelf or librarian-chef.
Developers unfamiliar with Chef are given a working example, just by running
- Developers may confuse the
- Non-custom cookbooks will be mixed together with your custom cookbook in the
- Developers may be tempted to deploy to production from this directory, so they'll either commit deployment info (IP addresses, node info, users, passwords, etc.) to Git, or keep them out of Git altogether by using
- Commits to this info might trigger unwanted continuous integration runs.
- It's easy to overlook the hidden
Solution #4 (best): embed cookbook inside your project; point to it from kitchen outside project
Recommended by me :-)
- Install Berkshelf and relevant Vagrant plugins:
gem install berkshelf --no-ri --no-rdoc vagrant plugin install vagrant-berkshelf vagrant plugin install vagrant-omnibus
- Create a cookbook directory named
berks cookbook cookbook cd cookbook bundle install
- Fill out
metadata.rbwith information for your cookbook. Cookbook dependencies will go here too, for example:
depends 'apt', '~> 2.3.4'
- Tweak the
Vagrantfileas needed, for example:
- Set the
- Change the
"http://files.vagrantup.com/precise32.box"or whatever your prefer, to avoid being dependent on the Berkshelf box.
config.omnibus.chef_version = :latestsince the
precise32box won't have chef auto-installed.
config.ssh.timeout, which are deprecated settings.
config.vm.boot_timeout = 120
- Add port forwarding if you prefer
- Change the recipe in the
cookbookto whatever you named the recipe in
- Set the
- Start the VM with
vagrant up. It will automatically provision itself using
chef-solousing the recipe at
- Test your app at http://18.104.22.168/ or http://localhost:8080/ or whatever you have set up.
- Iterate to improve your cookbook by editing
vagrant provision. Repeat as needed.
- When you're ready to deploy to staging or production,
cdto your kitchen in another directory outside this project, and in your kitchen's
Berksfile, refer to your project's cookbook by adding one of the following lines:
cookbook 'myproject', git: 'https://github.com/myname/myproject.git', rel: 'cookbook'
cookbook 'myproject', path: '/Users/myname/myproject/cookbook'
- Developers unfamiliar with Chef are given a working example, just by running
- Non-custom cookbooks are hidden at
~/.berkshelf/cookbooks, where developers are unlikely to edit them.
- The custom cookbook's directories (e.g.
files, etc.) are directly visible under
- The cookbook is stored in the same repo as the project, so the two can stay synchronized.
Well, it is a little confusing to have two
Berksfiles (one in your project, one in your kitchen) and three
Gemfiles (project dir, cookbook dir, kitchen).
Agree? Disagree? I'm new to Chef and would love to hear your insight in the comments below.