Skip to content
Chef Guide — Infrastructure as Code with Cookbooks and Recipes

Chef Guide — Infrastructure as Code with Cookbooks and Recipes

DodaTech Updated Jun 7, 2026 9 min read

Chef is a powerful infrastructure automation platform that turns infrastructure into code using Ruby-based DSL, enabling you to define, version, and manage your entire server fleet through reusable cookbooks, recipes, and resources.

What You’ll Learn

You’ll understand Chef’s three-tier architecture (server, workstation, node), write recipes in Ruby DSL, organize code with cookbooks, manage configuration data with data bags, gather system facts with Ohai, and run Chef Infra Client for desired-state convergence.

Why Chef Matters

Chef treats infrastructure as code — version-controlled, testable, and repeatable. Its Ruby-based DSL integrates naturally with existing DevOps toolchains and is especially powerful in organizations already using Ruby. DodaTech’s security scanning pipeline uses Chef to maintain consistent tooling across 150+ analysis nodes, ensuring every node has the same scanner versions, virus definitions, and output formats.

Chef Architecture

    flowchart LR
  A[Chef Workstation] --> B[Chef Server]
  B --> C[Chef Client Nodes]

  D[Cookbooks] --> A
  E[Recipes] --> A
  F[Data Bags] --> A
  G[Ohai Facts] --> C
  C --> G

  H[Node Objects] --> B
  C --> B

  A:::current

  classDef current fill:#F09800,color:#fff,stroke:#333,stroke-width:2px
  
Prerequisites: Basic system administration on Linux. Familiarity with Ruby syntax (variables, hashes, blocks) is highly recommended. Bash scripting experience is helpful.

Chef Concepts

Chef has three main components:

  • Chef Workstation — Where you write and test cookbooks
  • Chef Server — Central repository for cookbooks, node data, and policies
  • Chef Nodes — Managed servers running Chef Infra Client

The workflow: you write cookbooks on your workstation, upload them to the server, and nodes pull their configurations and converge to the desired state.

Setting Up Chef

# Install Chef Workstation (your local machine)
curl -L https://omnitruck.chef.io/install.sh | sudo bash -s -- -P chef-workstation

# Verify installation
chef --version

# Set up a cookbook repository
chef generate repo chef-repo
cd chef-repo
chef generate cookbook dodatech_base

Writing Recipes

Recipes are Ruby files that declare resources — the building blocks of system configuration:

# cookbooks/dodatech_base/recipes/default.rb

# Ensure the system is up to date
apt_update 'Update package cache' do
  action :update
  only_if { platform_family?('debian') }
end

# Install essential packages
package 'Install common tools' do
  package_name %w(curl wget git htop net-tools ufw fail2ban)
  action :install
end

# Configure firewall
firewall 'default' do
  action :install
end

firewall_rule 'allow-ssh' do
  port 22
  protocol :tcp
  action :allow
end

firewall_rule 'allow-http-https' do
  port [80, 443]
  protocol :tcp
  action :allow
end

# Create application user
user 'create-deploy-user' do
  username 'deploy'
  uid 2001
  home '/home/deploy'
  shell '/bin/bash'
  manage_home true
  action :create
end

# Set up SSH directory
directory '/home/deploy/.ssh' do
  owner 'deploy'
  group 'deploy'
  mode '0700'
  action :create
end

# Deploy authorized keys
file '/home/deploy/.ssh/authorized_keys' do
  content data_bag_item('users', 'deploy')['ssh_keys'].join("\n")
  owner 'deploy'
  group 'deploy'
  mode '0600'
end

Output: When Chef runs on a node, it ensures all 10+ packages are installed, the firewall allows only SSH/HTTP/HTTPS, the deploy user exists with SSH key authentication, and the authorized keys come from encrypted data bags.

Cookbooks and Templates

Cookbooks bundle recipes, templates, files, and metadata:

# cookbooks/dodatech_web/metadata.rb
name 'dodatech_web'
version '1.0.0'
depends 'nginx', '~> 10.0'
depends 'ssl_certificate', '~> 2.0'

supports 'ubuntu', '>= 20.04'
supports 'centos', '>= 7.0'
# cookbooks/dodatech_web/recipes/default.rb

# Include the nginx community cookbook
include_recipe 'nginx::default'

# Create the web root
directory '/var/www/dodatech' do
  owner 'www-data'
  group 'www-data'
  mode '0755'
  recursive true
  action :create
end

# Deploy the site configuration from template
template '/etc/nginx/sites-available/dodatech.conf' do
  source 'nginx-site.conf.erb'
  owner 'root'
  group 'root'
  mode '0644'
  variables(
    server_name: 'dodatech.com',
    root: '/var/www/dodatech',
    ssl: true
  )
  notifies :reload, 'service[nginx]'
end

# Enable the site
nginx_site 'dodatech.conf' do
  action :enable
end
<!-- templates/default/nginx-site.conf.erb -->
server {
    listen 80;
    server_name <%= @server_name %>;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name <%= @server_name %>;

    root <%= @root %>;
    index index.html index.htm;

    ssl_certificate     /etc/ssl/certs/dodatech.pem;
    ssl_certificate_key /etc/ssl/private/dodatech.key;
    ssl_protocols       TLSv1.2 TLSv1.3;

    location / {
        try_files $uri $uri/ =404;
    }

    location /api/ {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Output: Chef converges the node with Nginx installed, the custom site configuration deployed, the web root directory created, and the site enabled. Changing the template triggers an Nginx reload via the notifies declaration.

Ohai — System Facts

Ohai (Chef’s equivalent of Facter) collects system attributes:

# Use Ohai attributes in recipes
ohai 'reload' do
  action :reload
end

# Platform-specific logic
case node['platform_family']
when 'debian'
  package 'apache2' do
    action :install
  end
when 'rhel'
  package 'httpd' do
    action :install
  end
end

# Use attributes in templates
template '/etc/motd' do
  source 'motd.erb'
  variables(
    hostname: node['hostname'],
    fqdn: node['fqdn'],
    memory: node['memory']['total'],
    cpu: node['cpu']['total'],
    ip: node['ipaddress']
  )
end

Output: Chef automatically detects the target OS and installs the correct web server package. The Message of the Day template renders with the node’s hostname, IP, memory, and CPU info — available because Ohai collects them on every Chef run.

Data Bags

Data bags store JSON configuration data on the Chef Server:

# Create a data bag
knife data bag create users

# Create a data bag item
knife data bag from file users deploy.json
{
  "id": "deploy",
  "comment": "Deployment user",
  "uid": 2001,
  "gid": 2001,
  "shell": "/bin/bash",
  "ssh_keys": [
    "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ... deploy@workstation",
    "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ... ci@jenkins"
  ],
  "sudo_commands": ["/usr/bin/systemctl", "/usr/bin/journalctl"]
}
# Access data bag in recipe
users = data_bag('users')
users.each do |user_name|
  user_data = data_bag_item('users', user_name)

  user user_name do
    uid user_data['uid']
    gid user_data['gid']
    shell user_data['shell']
    home "/home/#{user_name}"
    manage_home true
    action :create
  end

  # Configure SSH
  directory "/home/#{user_name}/.ssh" do
    owner user_name
    group user_name
    mode '0700'
  end

  file "/home/#{user_name}/.ssh/authorized_keys" do
    content user_data['ssh_keys'].join("\n")
    owner user_name
    group user_name
    mode '0600'
  end
end

Output: Each user defined in the users data bag is created with their SSH keys deployed. Adding a new user means creating a JSON file in the data bag and re-running Chef — no recipe changes needed.

Chef Infra Client Run

The Chef run cycle executes in this order:

  1. Ohai collects node attributes
  2. Chef Infra Client connects to the Chef Server
  3. Node object is loaded with current run list
  4. Cookbooks are downloaded from the server
  5. Converge — resources are compared to desired state and actions taken
  6. Node object is saved back with updated attributes
  7. Report handlers run
# Run Chef on a node
sudo chef-client

# Dry run (see changes without applying)
sudo chef-client --why-run

# Run a specific recipe
sudo chef-client --runlist 'recipe[dodatech_base],recipe[dodatech_web]'

# Set up periodic runs (via cron)
sudo chef-client --daemonize

Security Angle: Using Encrypted Data Bags

# Create an encrypted data bag
knife data bag create secrets --secret-file ~/.chef/encrypted_data_bag_secret
knife data bag from file secrets database.json --secret-file ~/.chef/encrypted_data_bag_secret
# Access encrypted data in recipe
db_config = data_bag_item('secrets', 'database')
# Automatically decrypted on the Chef Server

# Use in templates or resources
template '/etc/app/database.yml' do
  variables(
    host: db_config['host'],
    port: db_config['port'],
    username: db_config['username'],
    password: db_config['password']
  )
  sensitive true  # Suppress output in logs
end

DodaTech uses this same pattern to distribute database credentials and API keys across environments without exposing secrets in version control or logs.

Common Mistakes Beginners Make

  1. Not testing with Test Kitchen: Chef code must be tested. Test Kitchen spins up VMs/Docker containers and runs your cookbooks, verifying they converge without errors.

  2. Imperative thinking: Chef is declarative. Don’t write if not installed, install. Just declare package 'nginx' with action :install — Chef handles idempotency.

  3. Hard-coding sensitive data: Never put passwords, API keys, or certificates in recipes. Use encrypted data bags or Chef Vault for secrets.

  4. Notifying services outside the cookbook: notifies only works within the same Chef run. If a resource is in a recipe that runs later, the notification may be missed.

  5. Over-using execute resources: execute runs arbitrary shell commands without idempotency guarantees. Prefer specialized resource types (package, service, file, template).

  6. Forgetting Ohai attribute reload: If you install a package that creates new system attributes, run ohai 'reload' before accessing those attributes in later recipes.

Practice Questions

  1. What is the difference between Chef Server and Chef Workstation?
  2. How do data bags differ from node attributes?
  3. What does --why-run do in Chef?
  4. Why should you use resource types instead of execute?
  5. How do you make a recipe platform-agnostic?

Answers:

  1. The Chef Server stores cookbooks, node data, and policies centrally. The Workstation is where you develop, test, and upload cookbooks. Nodes run Chef Infra Client to pull configurations.
  2. Data bags are stored on the Chef Server and accessible across nodes. Node attributes are collected per-node by Ohai and are specific to that machine.
  3. --why-run performs a dry run — it shows what changes Chef would make without applying them. Useful for auditing and planning.
  4. Resource types (package, service, file) are inherently idempotent and handle platform differences. execute runs arbitrary commands without these guarantees.
  5. Use Ohai attributes like node['platform_family'] for conditional logic, and use resource-level guards like only_if and not_if.

Challenge

Build a complete multi-tier application stack with Chef: create separate cookbooks for the web tier (Nginx, application), data tier (PostgreSQL), and cache tier (Redis). Use data bags for environment configuration, encrypted data bags for database passwords, and Test Kitchen for convergence testing across Ubuntu and CentOS.

Real-World Task

Create a Chef cookbook for a CI/CD build agent: install Java/Python/Node.js, configure Docker with non-root access, deploy SSH keys for repository access, set up artifact caching directories, enforce resource limits via systemd, and register the node with a monitoring system.

FAQ

What is the difference between Chef and Puppet?
: Chef uses Ruby DSL with a client-server architecture. Puppet uses its own declarative DSL. Chef is more flexible (full programming language), while Puppet enforces stricter desired-state declarations.
Do I need Ruby experience for Chef?
: Basic cookbooks can be written by copying patterns. Advanced customization (custom resources, libraries) requires Ruby knowledge. The DSL is Ruby-based, so Ruby familiarity is valuable.
How does Chef handle secrets?
: Through encrypted data bags or Chef Vault. Data bags are encrypted with a shared secret key. Chef Vault extends this with per-node key management.
Can Chef manage Windows?
: Yes. Chef has native Windows resources for registry keys, services, packages, and scheduled tasks. The same DSL works across Linux and Windows.
How often does Chef run on nodes?
: Typically every 30 minutes via cron or chef-client daemon. You can trigger runs manually with sudo chef-client or configure the interval in client.rb.

Try It Yourself

# Install Chef Workstation
curl -L https://omnitruck.chef.io/install.sh | sudo bash -s -- -P chef-workstation

# Generate a cookbook and run it locally
chef generate cookbook dodatech_test
cd dodatech_test

# Add a simple resource to recipes/default.rb:
# file '/tmp/chef-test.txt' do
#   content "Chef works!\n"
#   mode '0644'
# end

# Apply locally (requires Chef Infra Client)
sudo chef-client --local-mode --runlist 'recipe[dodatech_test]'
cat /tmp/chef-test.txt

What’s Next

Related topics: Ruby, Bash, Linux, Puppet

What’s Next

Congratulations on completing this Chef tutorial! Here’s where to go from here:

  • Practice daily — Consistency is more important than long study sessions
  • Build a project — Apply what you learned by building something real
  • Explore related topics — Check out other tutorials in the same category
  • Join the community — Discuss with other learners and share your progress

Remember: every expert was once a beginner. Keep coding!

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro