Learn the complete structure of a cookbook and the role of each directory such as recipes, attributes, templates, files, libraries, and metadata.rb, as well as how to compose a run_list per node, roles, and environments to group nodes consistently.

In episode 5 you learned about attributes and Ohai, two mechanisms that let a cookbook adapt to each node's conditions. You know how to read values like node['hostname'] and node['platform'], and understand how attributes flow from various levels until they produce the final value when chef-client runs.
You might be wondering: now that you have many recipes, where should they be stored? How does the server know which recipes should run for a particular node? The answer lies in cookbooks, run_list, and the grouping mechanisms roles and environments.
Episode 6 will dissect the cookbook directory structure, from recipes/ to metadata.rb, then explain how the run list determines the execution order of recipes per node. By the end of the episode you'll understand how to group nodes with roles and environments so that one configuration can serve many servers without rewriting.
A cookbook is the smallest distribution unit in Chef. A cookbook can contain recipes, default attributes, templates, static files, Ruby helpers, and metadata describing its dependencies. When you run chef generate cookbook web_server, Chef Workstation creates the following structure:
web_server/
├── attributes/ # default attributes per cookbook
├── files/ # static files copied as-is
├── libraries/ # Ruby helpers (custom code)
├── metadata.rb # cookbook description & dependencies
├── recipes/ # main cookbook recipes
├── templates/ # ERB-based configuration files
└── TESTING.md # testing documentationHere's the role of each directory:
recipes/ — the heart of the cookbook. Each Ruby file declares resources whose state you want to guarantee. default.rb is the entry point that runs when the cookbook is invoked without a recipe name.attributes/ — default attributes stored centrally per cookbook. Values here are read via the default['web_server']['port'] pattern and can be overridden by roles, environments, or node attributes.templates/ — ERB templates rendered with node variables, great for config files whose values depend on the machine.files/ — static files such as binaries, certificates, or fixed configuration copied as-is without processing.libraries/ — custom Ruby code such as classes, modules, or helper methods that can be reused across recipes.metadata.rb — the cookbook's identity card: name, version, maintainer, dependencies, and supported platforms.The metadata.rb file is the part beginners most often overlook, even though the chef-server uses it to resolve dependencies. A typical example:
name 'web_server'
maintainer 'Arman Dwi Pangestu'
maintainer_email 'devnull@example.com'
license 'Apache-2.0'
description 'Manages web server configuration'
version '0.1.0'
depends 'nginx', '>= 1.18'
supports 'ubuntu', '>= 22.04'The depends declaration tells the server that this cookbook needs the nginx cookbook at a specific version. Without correct metadata, the cookbook upload in episode 8 could fail because dependencies aren't satisfied.
Note
All the directories above are optional. The smallest cookbook only needs metadata.rb and one file in recipes/. The other directories are produced by chef generate cookbook to keep the working pattern consistent, but they can be removed if unused.
A run list is an ordered list of recipes or roles that chef-client will run on a node. Execution order matters: resources in the first recipe are executed first, then it continues to the next one.
To assign a run list to a node from the workstation, use knife node run_list set:
knife node run_list set web01 'recipe[apt],recipe[web_server]'Afterwards, you can verify the node's run list contents:
knife node show web01 -a run_listThe recipe notation in a run list takes two forms: recipe[web_server] means calling recipes/default.rb, while recipe[web_server::nginx] means calling recipes/nginx.rb.
Tip
The recipe[] and role[] symbols are an example of inline hints also used in Chef documentation: recipe[web_server::nginx] references the file recipes/nginx.rb, while recipe[web_server] is the same as recipes/default.rb. The file name must exactly match the part after the two colons.
A role groups nodes by their function, for example webserver, database, or load_balancer. A role contains a run list and default attributes that automatically merge into all nodes using that role. Here's an example role file:
name 'webserver'
description 'Node that runs a web server'
run_list(
'recipe[apt]',
'recipe[web_server]'
)
default_attributes(
'web_server' => {
'worker_processes' => 4
}
)Upload the role to the server, then assign it to a node in one step:
knife role from file roles/webserver.rb
knife node run_list set web01 'role[webserver]'An environment groups nodes by lifecycle stage: development, staging, and production. An environment has higher authority in the attribute hierarchy because it uses the override precedence, and it can lock which cookbook versions a node is allowed to run.
name 'production'
description 'Production environment'
cookbook_versions(
'web_server' => '~> 0.1.0'
)
override_attributes(
'web_server' => {
'worker_processes' => 16
}
)Upload and assign the environment to a node:
knife environment from file environments/production.rb
knife node environment set web01 productionBeginners often confuse the two. The following table summarizes the core differences:
| Aspect | Roles | Environments |
|---|---|---|
| Main function | Node role (web, database) | Lifecycle stage (dev, prod) |
| Contents | run_list + attributes | cookbook versions + attributes |
| Attribute precedence | default | override (higher) |
| Count per node | Many roles allowed | Only one environment |
| Example | role[webserver] | production |
Important
A node can only be in one environment, but it can have many roles. The environment governs which cookbook versions the node may use, while roles determine the node's function and run list. Combining the two produces structured configuration without duplication.
In episode 6 you understood the anatomy of a cookbook, the role of each directory from recipes/ to metadata.rb, and how the run list determines execution order. You also saw how roles and environments separate function from lifecycle stage, so a single cookbook can be used consistently across hundreds of nodes with different roles and environments.
Key takeaways:
recipes/ as the entry point and metadata.rb as its identity and dependency list.In the next episode, episode 7, we move to the topic that makes cookbooks truly dynamic: Templates & Data Bags. You'll learn ERB templates that render configuration files based on node attributes, plus data bags for storing structured data on the server and chef-vault for securing secrets like passwords and API keys. Keep the momentum going.