In this episode we will dissect the eight basic Chef resources along with their properties and actions, how to compose recipes declaratively, and how idempotency works on each resource.

In episode 3 you successfully created the nginx cookbook and converged it onto a test node, and touched the first two resources: package and service. Now it's time to complete that ability by understanding resources as the core language of Chef and recipes as the way to compose them.
Episode 4 is the most fundamental episode of the whole series — because whatever you want to do with Chef, from installing packages to managing users, it's all resources. We will cover the eight basic resources (package, service, file, template, execute, directory, user, group), their properties and actions, how to compose recipes declaratively, and how idempotency works behind every resource.
A resource is a declarative statement about a desired state. Every resource has three main parts: a resource type (e.g. package), a resource name (unique identifier), and a block of properties & actions that describe the desired state.
package 'nginx' do
version '1.24.0' # property
action :install # action
endWhen chef-client encounters this resource, it doesn't execute immediately. It collects the current state (e.g. "nginx is not installed"), compares it with the desired state, and only acts if they differ. This is why a resource is called declarative — we describe the end result, not the steps.
Here are the eight resources most commonly used in the real world, complete with their main properties and actions.
Manages software packages, automatically using the package manager appropriate for the platform (apt, yum, dnf).
package 'nginx' do
version '1.24.0'
action :install
endControls system services/daemons. The action property with an array allows multiple actions at once.
service 'nginx' do
action [:enable, :start]
endManages files: creating, writing content, or deleting. The file resource writes content as-is (without templating).
file '/etc/motd' do
content 'Welcome to a Chef-managed node'
mode '0644'
owner 'root'
group 'root'
action :create
endLike file, but its content is generated from an ERB template that can contain node variables. Templates will be discussed in depth in episode 7.
template '/etc/nginx/sites-available/default' do
source 'default.erb'
variables(
server_name: node['hostname']
)
notifies :reload, 'service[nginx]'
endRuns an arbitrary shell command. Use it carefully because it is not idempotent automatically — it needs the not_if or only_if property.
execute 'update-nginx-cache' do
command 'nginx -s reload'
action :run
not_if 'test ! -f /etc/nginx/nginx.conf'
endCaution
execute is the most "imperative" resource and is not idempotent automatically. Always attach not_if (run if the condition is not met) or only_if (run only if the condition is met). If there is a dedicated resource that can replace the shell command, use that first.
Manages directories, including nested ones with recursive.
directory '/var/www/example' do
owner 'www-data'
group 'www-data'
mode '0755'
recursive true
action :create
endManages system users.
user 'deploy' do
comment 'Application deploy account'
home '/home/deploy'
shell '/bin/bash'
uid 1001
action :create
endManages system groups.
group 'developers' do
members ['deploy', 'ops']
append true
action :create
end| Resource | Function | Main actions | Idempotent |
|---|---|---|---|
package | Install/manage software | :install, :upgrade, :remove | Yes (automatic) |
service | Manage daemons/services | :start, :stop, :restart, :enable | Yes (automatic) |
file | Manage files and their content | :create, :touch, :delete | Yes (automatic) |
template | Generate files from ERB templates | :create | Yes (automatic) |
execute | Run shell commands | :run, :nothing | No (needs not_if/only_if) |
directory | Manage directories | :create, :delete | Yes (automatic) |
user | Manage system users | :create, :lock, :remove | Yes (automatic) |
group | Manage system groups | :create, :manage, :remove | Yes (automatic) |
A recipe is just a collection of resources arranged in order. Although resource order affects execution order, it's important to keep thinking declaratively: describe the end state, not the procedure. Compare two ways of thinking about the same task — install nginx, create a directory, and place an index file:
package 'nginx' do
action :install
end
directory '/var/www/example' do
owner 'www-data'
mode '0755'
recursive true
action :create
end
file '/var/www/example/index.html' do
content '<h1>Hello Chef!</h1>'
mode '0644'
action :create
end
service 'nginx' do
action [:enable, :start]
endNotice the difference: the Chef version is a bit longer in code, but it guarantees idempotency. The shell version must be run with extreme care and is not safe to run twice.
Tip
The notifies property allows resources to communicate with each other: notifies :reload, 'service[nginx]' on a template resource means that every time the config file changes, the nginx service is automatically reloaded — without writing an explicit sequence.
Let's practice the concept we've discussed since episode 1. Converge the recipe twice and observe the output:
cd ~/lab-chef/chef-repo
knife cookbook upload nginx
knife ssh 'name:node-01' 'sudo chef-client' --ssh-user devops# First run: changes present
* directory[/var/www/example] action create
- create new directory /var/www/example
* service[nginx] action start
- start service service[nginx]
Chef Infra Client finished, 3/3 resources updated
# Second run: everything up to date
* directory[/var/www/example] action create
- up to date
* service[nginx] action start
- up to date
Chef Infra Client finished, 0/3 resources updatedImportant
The key phrase is 0/3 resources updated on the second run. That's the proof of idempotency: resources never do more than necessary. If your second-run output still shows updated, something is wrong with how you wrote the resource — investigate its properties.
In episode 4 we mastered the core language of Chef: the eight basic resources, their properties and actions, how to compose recipes declaratively, and proof of idempotency working for real.
Key takeaways:
execute is not idempotent — always protect it with not_if or only_if.notifies connects resources — a change to a config file automatically reloads the service.0/... resources updated on the second run.You can now write safe, declarative recipes. In the next episode, episode 5, we will study attributes & Ohai — attribute levels and their precedence order (default, force_default, normal, override, automatic), how Ohai plugins collect system facts (OS, memory, network), and how to read node attributes inside a recipe.