In this episode we will dissect node attributes: the five attribute levels and their precedence order, how Ohai collects system facts, and how to read attributes inside a recipe.

In episode 4 you mastered resources and recipes — the core language for describing what you want. But there's a question that inevitably arises once your recipes get complex: how do you make one cookbook behave differently on different nodes? The answer lies in attributes.
Episode 5 dissects two concepts that are often considered hard but are actually very functional: node attributes (data that describes a node and drives recipe behavior) and Ohai (the engine that collects that data). We will cover the five attribute levels and their precedence order — where the values come from and who wins when there's a conflict — as well as how to read attributes inside a recipe.
An attribute is a key-value pair attached to a node. Think of an attribute as a node's "ID card": what operating system, how much memory, what IP address, what hostname, and what settings the cookbook should use.
Attributes flow from many sources: Ohai (automatic), attribute files in cookbooks, environments, roles, and attributes set directly on the node. The question then becomes: if many sources set a value for the same key, who wins? That's what precedence governs.
Chef recognizes several attribute levels. At this early stage of the series, focus on the following five main levels, ordered from the lowest to the highest priority:
| Level | Priority | Where it comes from | Example |
|---|---|---|---|
default | 1 (lowest) | Cookbook attribute file | default['nginx']['port'] = 80 |
force_default | 2 | Attribute file / node | forces overriding default |
normal | 3 | Set on the node | knife node edit |
override | 4 | Attribute file / role / env | override['nginx']['port'] = 8080 |
automatic | 5 (highest) | Ohai only | automatic['ipaddress'] |
The golden rule: a higher-priority level overrides a lower one. automatic (Ohai) always wins, default always loses.
1. default (cookbook attribute) -- easiest to override
2. force_default (forces the default value)
3. normal (set directly on the node)
4. override (role / environment / cookbook)
5. automatic (Ohai) -- cannot be overridden by cookbooksTip
A practical rule: use default for baseline values in the cookbook, override in the environment for differences between stages (dev vs prod), and let automatic (Ohai) do its own work. You will rarely need force_default or normal early in your learning.
To understand precedence, we need to know where each level comes from:
default — declared in the cookbook's attributes/default.rb.force_default — also from attribute files, but given higher priority.normal — stored in the node object on the server, usually via knife node edit node-01.override — from a cookbook attribute file, role, or environment.automatic — entirely from Ohai, never written manually.Example of a declaration in a cookbook attribute file:
default['nginx']['port'] = 80
default['nginx']['worker_processes'] = 2
default['nginx']['server_name'] = 'example.com'Ohai is the tool chef-client runs in the first phase (remember episode 2) to collect node facts into automatic attributes. Ohai works through plugins — each plugin is responsible for one area of data.
The most commonly used Ohai plugins:
| Plugin | Data collected |
|---|---|
os | OS, platform, platform family, version |
memory | Total and available memory |
network | Interfaces, IP addresses, gateway |
cpu | Architecture, number of cores |
hostname | Hostname and FQDN |
disk | Partitions and disk space |
languages | Runtime versions (Ruby, Python, etc.) |
platform: ubuntu
platform_version: 22.04
memory.total: 3.82GB
cpu.total: 2
hostname: node-01
network.interfaces.eth0.addresses: 10.0.0.11To view Ohai data directly on a node:
ohai platform
ohai memory
ohai network/ipaddressNote
Ohai stores its data as automatic attributes. Because automatic has the highest precedence, Ohai data cannot be overridden by a cookbook — and that's how it should be. A node's OS is a fact, not a wish.
If the built-in data isn't enough, you can write your own Ohai plugin. Plugins are written in Ruby and placed in the cookbook's ohai/plugins/ directory.
Ohai.plugin(:AppVersion) do
provides 'app_version'
collect_data(:linux) do
app_version(
shell_out('cat /opt/app/version.txt').stdout.strip
) if File.exist?('/opt/app/version.txt')
end
endThe plugin above reads an application version from a file and publishes it as an app_version attribute. Once loaded, recipes can read it like any other attribute.
Now for the practical part: how recipes use attributes. There are two ways — reading node attributes, and reading attributes from a cookbook.
# Automatic attribute from Ohai
log node['platform'] do
level :info
end
# Combination for branching decisions
if node['platform_family'] == 'debian'
package 'nginx' do
action :install
end
end
# Default attribute from your own cookbook
log "nginx port: #{node['nginx']['port']}" do
level :info
endWarning
Pay attention to the quotes: attributes are accessed with brackets and string keys, for example node['nginx']['port']. Make sure the key you read is already declared in the cookbook's attribute file — reading a key that doesn't exist returns nil and can make the recipe behave unexpectedly.
Let's put it all together. The following recipe uses Ohai attributes to adapt the package and service name to the platform:
webserver_package = node['platform_family'] == 'debian' ? 'nginx' : 'httpd'
package webserver_package do
action :install
end
service webserver_package do
action [:enable, :start]
end
# Use the cookbook's default attributes for configuration
template '/etc/nginx/conf.d/server.conf' do
source 'server.conf.erb'
variables(
server_name: node['nginx']['server_name'],
port: node['nginx']['port']
)
notifies :restart, "service[#{webserver_package}]"
endknife node edit node-01{
"name": "node-01",
"normal": {
"nginx": {
"port": 8080
}
}
}Tip
With node-level attributes (normal), the same cookbook can be used on different nodes with different behavior — node-01 uses port 8080, node-02 stays on 80. That's the power of "one code, many configurations" at the heart of infrastructure as code.
For debugging, you can view all of a node's attributes at once:
knife node show node-01 --format jsonIn episode 5 we dissected the two data foundations of Chef: node attributes with their five levels and precedence (default, force_default, normal, override, automatic), how Ohai and its plugins collect system facts, and how to read attributes inside a recipe to make cookbooks adaptive.
Key takeaways:
automatic attributes.With attributes and Ohai, your cookbooks are now adaptive to node differences. In the next episode, episode 6, we will discuss cookbooks & run lists — the complete cookbook directory structure (recipes, attributes, templates, files, libraries, metadata.rb), how to compose a run_list per node, and the role of roles and environments for grouping nodes.