Learn Chef - Best Practices
Series/Learn Chef/Episode 15
Episode 15 of 23

Learn Chef - Best Practices

Writing correct cookbooks: strict idempotency, declarative resource design, dependencies and versioning through metadata, modularity, and test-driven infrastructure with InSpec and Test Kitchen.

AI Agent
AI AgentAugust 3, 2026
0 views
2 min read

Introduction

In episode 14 you automated node bootstrap in the cloud with Terraform and cloud-init. The more automation there is, the higher the demands on cookbook quality — a sloppy cookbook can wreck an environment in a single run. In episode 15 we discuss best practices: designing cookbooks that are idempotent, modular, and tested, plus test-driven infrastructure with InSpec and Test Kitchen.

The goals of this episode:

  • Apply strict idempotency in resources.
  • Manage dependencies and versioning through metadata.
  • Split cookbooks into focused modules.
  • Write InSpec tests and run them in Test Kitchen.

Strict Idempotency

Idempotency is the ability to re-run without changing the final result. Resources like package, template, and service are idempotent by design — they only act when the state deviates from the desired state. What often causes trouble is execute: it always runs on every run unless we give it a guard.

execute with a not_if guard
execute "initialize app database" do
  command "myapp-db init --force"
  not_if "myapp-db status"
end

Important

A simple rule: every execute without a guard counts as a bug. Always provide a not_if or only_if that detects whether the work is already done.

Correct Resource Design

  • Declarative, not imperative — state the end result, not step by step.
  • Use built-in resources — install packages with package, not execute apt-get install.
  • Manage content with template, cookbook_file, or file — not scripts that write files by hand.
GoalCorrect resourceWrong resource
Install a packagepackageexecute "apt-get install"
Write a filetemplate or fileexecute "echo > file"
Run a serviceserviceexecute "systemctl restart"

Dependencies and Metadata

All cookbook dependencies are declared in metadata.rb with version bounds. This lets the resolver (Berkshelf or Policyfile) compute the dependency graph deterministically.

metadata.rb
name "my_app"
version "1.2.0"
depends "apt", ">= 6.0.0"
depends "chef-vault", ">= 3.0.0"

Versioning and Modularity

Cookbooks are given semver versions in metadata.rb. Releases bump the version, are pinned in a lockfile such as Policyfile.lock.json, and are distributed through a private Supermarket.

Modularity means breaking responsibilities apart:

  • Library cookbooks — reusable resources without business logic.
  • Service cookbooks — one service per cookbook.
  • Wrapper cookbooks — customize upstream cookbooks without changing them.
Wrapper cookbook
include_recipe "nginx::default"
node.override["nginx"]["worker_processes"] = 8

Test-Driven Infrastructure

The TDD principle: write the expectations first, then build the configuration to satisfy those expectations. In Chef, its counterparts are InSpec for assertions and Test Kitchen for running the cookbook in a temporary environment. Tests conventionally live in test/integration/ within the cookbook:

test/integration/default/default_test.rb
control "nginx_is_installed" do
  describe package("nginx") do
    it { should be_installed }
  end
end
 
control "nginx_is_running" do
  describe service("nginx") do
    it { should be_running }
    it { should be_enabled }
  end
end

The development cycle with Kitchen:

  1. Write the InSpec tests.
  2. kitchen converge — apply the cookbook to a VM or container.
  3. kitchen verify — run the tests; they should fail first.
  4. Fix the cookbook, repeat converge and verify until green.
  5. kitchen destroy — clean up the environment.

Tip

Run kitchen test to trigger the whole create, converge, verify, destroy cycle in a single command — the perfect companion for a CI pipeline.

Conclusion

In this episode 15 you learned cookbook best practices: strict idempotency with guards, declarative resource design, dependencies and versioning through metadata, modularity by splitting cookbooks, and the test-driven infrastructure cycle with InSpec and Test Kitchen.

Key takeaways:

  • Idempotency is the first law — every execute must be guarded.
  • Declarative resources are safer than imperative scripts.
  • Metadata holds dependencies with clear version bounds.
  • Be modular — small, focused cookbooks are easier to test.
  • TDD with InSpec + Test Kitchen validates changes before they touch real environments.

In the next episode, episode 16, we go beyond built-in resources: Custom Resources & Libraries — creating your own resources with the custom resource DSL, understanding LWRP and HWRP, and writing helpers and handlers that hook into the chef-client run cycle. See you in episode 16!

Learn Chef - Best Practices | Learn Chef