Learn Chef - Resources & Recipes
Series/Learn Chef/Episode 4
Episode 4 of 23

Learn Chef - Resources & Recipes

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.

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

Introduction

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.

Resource: The Smallest Unit of Chef

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.

Anatomy of a resource
package 'nginx' do
  version '1.24.0'     # property
  action :install      # action
end

When 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.

The Eight Basic Resources

Here are the eight resources most commonly used in the real world, complete with their main properties and actions.

package

Manages software packages, automatically using the package manager appropriate for the platform (apt, yum, dnf).

package resource
package 'nginx' do
  version '1.24.0'
  action :install
end

service

Controls system services/daemons. The action property with an array allows multiple actions at once.

service resource
service 'nginx' do
  action [:enable, :start]
end

file

Manages files: creating, writing content, or deleting. The file resource writes content as-is (without templating).

file resource
file '/etc/motd' do
  content 'Welcome to a Chef-managed node'
  mode '0644'
  owner 'root'
  group 'root'
  action :create
end

template

Like 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 resource
template '/etc/nginx/sites-available/default' do
  source 'default.erb'
  variables(
    server_name: node['hostname']
  )
  notifies :reload, 'service[nginx]'
end

execute

Runs an arbitrary shell command. Use it carefully because it is not idempotent automatically — it needs the not_if or only_if property.

execute resource
execute 'update-nginx-cache' do
  command 'nginx -s reload'
  action :run
  not_if 'test ! -f /etc/nginx/nginx.conf'
end

Caution

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.

directory

Manages directories, including nested ones with recursive.

directory resource
directory '/var/www/example' do
  owner 'www-data'
  group 'www-data'
  mode '0755'
  recursive true
  action :create
end

user

Manages system users.

user resource
user 'deploy' do
  comment 'Application deploy account'
  home '/home/deploy'
  shell '/bin/bash'
  uid 1001
  action :create
end

group

Manages system groups.

group resource
group 'developers' do
  members ['deploy', 'ops']
  append true
  action :create
end

Resource Summary Table

ResourceFunctionMain actionsIdempotent
packageInstall/manage software:install, :upgrade, :removeYes (automatic)
serviceManage daemons/services:start, :stop, :restart, :enableYes (automatic)
fileManage files and their content:create, :touch, :deleteYes (automatic)
templateGenerate files from ERB templates:createYes (automatic)
executeRun shell commands:run, :nothingNo (needs not_if/only_if)
directoryManage directories:create, :deleteYes (automatic)
userManage system users:create, :lock, :removeYes (automatic)
groupManage system groups:create, :manage, :removeYes (automatic)

Composing Recipes Declaratively

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]
end

Notice 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.

Testing Idempotency

Let's practice the concept we've discussed since episode 1. Converge the recipe twice and observe the output:

Converge the cookbook twice
cd ~/lab-chef/chef-repo
knife cookbook upload nginx
knife ssh 'name:node-01' 'sudo chef-client' --ssh-user devops
First run vs second run
# 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 updated

Important

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.

Conclusion

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:

  • A resource is a desired state statement — describe the end result, not the steps.
  • The eight basic resources cover almost all everyday server configuration needs.
  • 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.
  • The idempotency marker is 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.