๐Ÿ‘จโ€๐Ÿณ

Chef

Chef: knife, cookbooks, recipes, resources, roles, environments, data bags, and Test Kitchen

knife โ€” Core Commands

Manage nodes, cookbooks, roles, and environments from the workstation

bashยทNode management
# List all nodes
knife node list

# Show node details
knife node show web-01
knife node show web-01 -a run_list
knife node show web-01 -a automatic.ipaddress

# Edit a node's run list
knife node edit web-01

# Set run list directly
knife node run_list set web-01 "role[base],role[webserver]"

# Add to run list
knife node run_list add web-01 "recipe[nginx]"

# Remove from run list
knife node run_list remove web-01 "recipe[nginx]"

# Delete a node
knife node delete web-01 --yes

# Delete node and client together
knife node delete web-01 --yes && knife client delete web-01 --yes
bashยทBootstrap a node
# Bootstrap a Linux node over SSH
knife bootstrap 10.0.0.1 \
  --ssh-user ubuntu \
  --sudo \
  --identity-file ~/.ssh/id_rsa \
  --node-name web-01 \
  --run-list "role[base],role[webserver]"

# Bootstrap with a specific Chef version
knife bootstrap 10.0.0.1 \
  --ssh-user ubuntu \
  --sudo \
  --identity-file ~/.ssh/id_rsa \
  --node-name web-01 \
  --bootstrap-version 17.10.0 \
  --run-list "role[base]"

# Bootstrap a Windows node (WinRM)
knife bootstrap windows winrm 10.0.0.2 \
  --winrm-user Administrator \
  --winrm-password 'P@ssword' \
  --node-name win-01 \
  --run-list "role[windows_base]"
bashยทSearch
# Search all nodes
knife search node "*:*"

# Search by role
knife search node "role:webserver"

# Search by environment
knife search node "chef_environment:production"

# Search by attribute
knife search node "platform:ubuntu AND platform_version:22.04"

# Search by name pattern
knife search node "name:web-*"

# Combine conditions
knife search node "role:webserver AND chef_environment:prod"

# Show only specific attributes
knife search node "role:webserver" -a ipaddress -a hostname

# Search data bags
knife search users "groups:admins"

# Count results only
knife search node "role:webserver" -i

Cookbooks

Create, upload, download, and manage cookbooks

bashยทManage cookbooks
# Generate a new cookbook
chef generate cookbook my_cookbook
knife cookbook create my_cookbook  # legacy

# List cookbooks on Chef Server
knife cookbook list

# Show versions of a cookbook
knife cookbook show my_cookbook

# Show a specific version
knife cookbook show my_cookbook 1.2.0

# Upload a cookbook to Chef Server
knife cookbook upload my_cookbook

# Upload all cookbooks
knife cookbook upload --all

# Upload with dependencies
knife cookbook upload my_cookbook --include-dependencies

# Download a cookbook from Chef Server
knife cookbook download my_cookbook
knife cookbook download my_cookbook 1.2.0

# Delete a cookbook version
knife cookbook delete my_cookbook 1.0.0 --yes

# Delete all versions
knife cookbook delete my_cookbook --all --yes
bashยทCookbook structure
# Generated cookbook layout:
# my_cookbook/
#   metadata.rb          โ€” name, version, dependencies
#   README.md
#   recipes/
#     default.rb         โ€” default recipe
#   attributes/
#     default.rb         โ€” default attribute values
#   templates/
#     default/           โ€” .erb template files
#   files/
#     default/           โ€” static files
#   resources/           โ€” custom resources (HWRP)
#   libraries/           โ€” Ruby helper modules
#   test/
#     integration/       โ€” InSpec / ServerSpec tests
#   spec/
#     unit/              โ€” ChefSpec unit tests
#   Berksfile            โ€” dependency management
#   .kitchen.yml         โ€” Test Kitchen config
#   chefignore           โ€” files to exclude from upload

# Generate components
chef generate recipe my_cookbook webserver
chef generate attribute my_cookbook default
chef generate template my_cookbook nginx.conf
chef generate resource my_cookbook app_service

Recipes & Resources

Common built-in resources and recipe patterns

rubyยทCommon built-in resources
# package โ€” install/remove OS packages
package 'nginx' do
  action :install
  version '1.24.0'          # optional: pin version
end

package %w[git curl wget] do
  action :install
end

# service โ€” manage system services
service 'nginx' do
  action [:enable, :start]  # enable on boot + start now
  supports status: true, restart: true, reload: true
end

# file โ€” manage file content
file '/etc/motd' do
  content "Managed by Chef
"
  owner   'root'
  group   'root'
  mode    '0644'
  action  :create
end

# directory โ€” create directories
directory '/var/app/releases' do
  owner     'deploy'
  group     'deploy'
  mode      '0755'
  recursive true
  action    :create
end

# template โ€” render ERB templates
template '/etc/nginx/nginx.conf' do
  source  'nginx.conf.erb'
  owner   'root'
  group   'root'
  mode    '0644'
  variables(
    worker_processes: node['nginx']['worker_processes'],
    port:             node['nginx']['port']
  )
  notifies :reload, 'service[nginx]', :delayed
end
rubyยทMore resources & patterns
# cookbook_file โ€” deploy a static file from files/ directory
cookbook_file '/etc/app/config.yml' do
  source 'config.yml'
  owner  'app'
  mode   '0640'
  action :create
end

# remote_file โ€” download from a URL
remote_file '/tmp/installer.tar.gz' do
  source   'https://releases.example.com/app-1.0.tar.gz'
  checksum 'sha256:abc123...'
  action   :create_if_missing
end

# execute โ€” run a shell command
execute 'run db migrations' do
  command 'bundle exec rake db:migrate'
  cwd     '/var/app/current'
  user    'deploy'
  environment 'RAILS_ENV' => 'production'
  action  :run
  not_if  { ::File.exist?('/var/app/.migrated') }
end

# bash / ruby_block
bash 'compile from source' do
  code <<~BASH
    ./configure --prefix=/usr/local
    make -j$(nproc)
    make install
  BASH
  cwd   '/tmp/src'
  action :run
  creates '/usr/local/bin/myapp'   # skip if file exists
end

# link โ€” manage symlinks
link '/usr/local/bin/node' do
  to '/usr/local/node-18/bin/node'
end

# user & group
group 'deploy' do
  gid    1500
  action :create
end

user 'deploy' do
  uid     1500
  gid     'deploy'
  home    '/home/deploy'
  shell   '/bin/bash'
  action  :create
end
rubyยทNotifications & guards
# notifies โ€” trigger another resource after this one changes
template '/etc/nginx/nginx.conf' do
  source 'nginx.conf.erb'
  notifies :reload,  'service[nginx]', :delayed    # after converge
  notifies :restart, 'service[nginx]', :immediately # right now
end

# subscribes โ€” this resource reacts when another changes
service 'nginx' do
  action [:enable, :start]
  subscribes :reload, 'template[/etc/nginx/nginx.conf]', :delayed
end

# Guards โ€” only_if / not_if
package 'redis' do
  action  :install
  only_if { node['app']['cache_enabled'] }
end

execute 'init database' do
  command '/usr/bin/init-db.sh'
  not_if  'systemctl is-active mydb'          # shell string
  not_if  { ::File.exist?('/var/db/.init') }  # Ruby block
end

# Lazy attribute evaluation
template '/etc/app.conf' do
  variables lazy {
    { secret: Chef::EncryptedDataBagItem.load('secrets', 'app')['key'] }
  }
end

Attributes

Define, override, and access node attributes

rubyยทAttribute precedence & definition
# attributes/default.rb โ€” define cookbook defaults

# Precedence levels (lowest โ†’ highest):
# default < force_default < normal < override < force_override < automatic

default['nginx']['port']             = 80
default['nginx']['worker_processes'] = 'auto'
default['nginx']['log_dir']          = '/var/log/nginx'

default['app']['user']     = 'deploy'
default['app']['dir']      = '/var/app'
default['app']['env']      = 'production'
default['app']['packages'] = %w[git curl libpq-dev]

# Nested hash
default['app']['db'] = {
  host: 'localhost',
  port: 5432,
  name: 'myapp'
}

# Override in a role or environment:
# override_attributes 'nginx' => { 'port' => 8080 }

# Access in a recipe:
# node['nginx']['port']
# node['app']['db']['host']
# node.default['nginx']['port'] = 8080  (set at runtime)

Roles

Create, upload, and apply roles to nodes

bashยทknife role commands
# List roles
knife role list

# Show a role
knife role show webserver

# Create / edit a role (opens $EDITOR)
knife role create webserver
knife role edit webserver

# Upload a role from a file
knife role from file roles/webserver.rb
knife role from file roles/*.rb

# Delete a role
knife role delete webserver --yes
rubyยทRole file (roles/webserver.rb)
name        'webserver'
description 'Web-facing application server'

# Run list applied to nodes with this role
run_list(
  'role[base]',
  'recipe[nginx]',
  'recipe[app::deploy]'
)

# Default attribute overrides (lower precedence)
default_attributes(
  'nginx' => {
    'worker_processes' => 4,
    'port'             => 80
  },
  'app' => {
    'env' => 'production'
  }
)

# Override attributes (higher precedence)
override_attributes(
  'nginx' => {
    'log_level' => 'warn'
  }
)

Environments

Pin cookbook versions and set attributes per environment

bashยทknife environment commands
# List environments
knife environment list

# Show an environment
knife environment show production

# Create / edit
knife environment create production
knife environment edit production

# Upload from file
knife environment from file environments/production.rb
knife environment from file environments/*.rb

# Move a node to an environment
knife node environment_set web-01 production

# Search nodes in an environment
knife search node "chef_environment:production"

# Delete an environment
knife environment delete staging --yes
rubyยทEnvironment file
name        'production'
description 'Production environment'

# Pin cookbook versions (prevents accidental upgrades)
cookbook_versions(
  'nginx'       => '= 4.0.0',
  'app'         => '>= 2.1.0',
  'base'        => '~> 1.5.0',   # >= 1.5.0 and < 2.0.0
  'postgresql'  => '= 8.0.0'
)

# Default attributes for this environment
default_attributes(
  'app' => {
    'env'      => 'production',
    'log_level'=> 'warn'
  }
)

# Override attributes
override_attributes(
  'nginx' => {
    'worker_processes' => 8
  }
)

Data Bags

Store and access shared JSON data and encrypted secrets

bashยทknife data bag commands
# Create a data bag
knife data bag create users
knife data bag create secrets

# List data bags
knife data bag list

# List items in a data bag
knife data bag show users

# Show an item
knife data bag show users alice

# Create an item from a file
knife data bag from file users alice.json
knife data bag from file users data_bags/users/*.json

# Edit an item
knife data bag edit users alice

# Delete an item
knife data bag delete users alice --yes

# Delete a data bag
knife data bag delete users --yes

# Create encrypted item (uses a secret key file)
knife data bag create secrets db_creds \
  --secret-file ~/.chef/encrypted_data_bag_secret

# Show encrypted item (decrypts with key)
knife data bag show secrets db_creds \
  --secret-file ~/.chef/encrypted_data_bag_secret
rubyยทAccess data bags in recipes
# Load a plain data bag item
users_bag = data_bag('users')        # returns array of item IDs
alice     = data_bag_item('users', 'alice')
Chef::Log.info("Alice's email: #{alice['email']}")

# Iterate over all items in a data bag
data_bag('users').each do |user_id|
  user = data_bag_item('users', user_id)
  user user_id do
    uid   user['uid']
    gid   user['gid']
    home  "/home/#{user_id}"
    shell user['shell'] || '/bin/bash'
    action :create
  end
end

# Load an encrypted data bag item
secret  = Chef::EncryptedDataBagItem.load_secret('/etc/chef/encrypted_data_bag_secret')
db_creds = Chef::EncryptedDataBagItem.load('secrets', 'db_creds', secret)

template '/etc/app/database.yml' do
  variables(
    host:     db_creds['host'],
    password: db_creds['password']
  )
end

Berkshelf

Manage cookbook dependencies with Berkshelf

bashยทberks commands
# Install dependencies from Berksfile (into ~/.berkshelf/cookbooks)
berks install

# Update all dependencies (re-resolves versions)
berks update

# Update a specific cookbook
berks update nginx

# Upload all cookbooks to Chef Server
berks upload

# Upload a specific cookbook
berks upload my_cookbook

# Upload without SSL verification
berks upload --no-ssl-verify

# Show resolved dependency list
berks list

# Visualize dependency graph
berks viz

# Check for newer versions available
berks outdated

# Package cookbooks into a tarball (for chef-zero / policyfiles)
berks package cookbooks.tar.gz
rubyยทBerksfile
# Berksfile โ€” cookbook dependency manifest

source 'https://supermarket.chef.io'

# Cookbook from Chef Supermarket
cookbook 'nginx', '~> 4.0'
cookbook 'postgresql', '>= 8.0'
cookbook 'apt', '~> 7.4'

# From a specific git repo and branch/tag
cookbook 'my_cookbook',
  git: 'https://github.com/myorg/my_cookbook.git',
  branch: 'main'

# From a specific git tag
cookbook 'my_cookbook',
  git: 'https://github.com/myorg/my_cookbook.git',
  tag: 'v1.2.0'

# From the local filesystem
cookbook 'internal_cookbook', path: '../internal_cookbook'

# The cookbook in this directory (typical when Berksfile is in a cookbook)
metadata

chef-client

Run, configure, and schedule the Chef client on a node

bashยทchef-client run modes
# Standard run (connects to Chef Server)
sudo chef-client

# Run with a specific log level
sudo chef-client --log_level info
sudo chef-client --log_level debug

# Run only specific recipes (override run list)
sudo chef-client --override-runlist "recipe[nginx],recipe[app]"

# Run in local mode (chef-zero, no server needed)
sudo chef-client --local-mode --runlist "recipe[my_cookbook]"
# or shorthand:
sudo chef-zero --local-mode -r "recipe[my_cookbook]"

# Dry run โ€” show what would change without applying
sudo chef-client --why-run

# Run once and exit (no daemonize)
sudo chef-client --once

# Force specific node name
sudo chef-client --node-name web-01

# Run from a specific config
sudo chef-client -c /etc/chef/client.rb
rubyยทclient.rb configuration
# /etc/chef/client.rb โ€” Chef client configuration

chef_server_url  'https://chef.example.com/organizations/myorg'
node_name        'web-01'
client_key       '/etc/chef/client.pem'
validation_client_name 'myorg-validator'
validation_key   '/etc/chef/validation.pem'

# Log settings
log_level        :info
log_location     '/var/log/chef/client.log'

# Run interval (seconds) when running as daemon
interval         1800
splay            300    # random delay up to N seconds (prevents thundering herd)

# SSL
ssl_verify_mode  :verify_peer
trusted_certs_dir '/etc/chef/trusted_certs'

# Cache paths
file_cache_path  '/var/chef/cache'
file_backup_path '/var/chef/backup'

# Data bag secret
encrypted_data_bag_secret '/etc/chef/encrypted_data_bag_secret'

# Ohai โ€” limit plugins for faster runs
ohai.disabled_plugins = [:Cloud, :EC2, :Azure]

Test Kitchen

Converge, verify, and destroy test instances

bashยทkitchen commands
# List all test instances and their status
kitchen list

# Full lifecycle: create + converge + verify
kitchen test

# Create the instance (VM / container)
kitchen create
kitchen create ubuntu-2204

# Converge โ€” run chef-client on the instance
kitchen converge
kitchen converge ubuntu-2204

# Run InSpec/Serverspec tests
kitchen verify
kitchen verify ubuntu-2204

# Login to a running instance
kitchen login ubuntu-2204

# Destroy the instance
kitchen destroy
kitchen destroy ubuntu-2204

# Re-run just the verify step after changes
kitchen verify ubuntu-2204

# Run everything except destroy (for debugging)
kitchen test --destroy=never
yamlยท.kitchen.yml
# .kitchen.yml โ€” Test Kitchen configuration

driver:
  name: vagrant              # or docker, ec2, azure, gcp, etc.
  customize:
    memory: 1024

provisioner:
  name: chef_zero            # or chef_infra, chef_solo
  product_name: chef
  product_version: latest
  always_update_cookbooks: true

verifier:
  name: inspec               # or busser, serverspec

platforms:
  - name: ubuntu-22.04
    driver:
      box: bento/ubuntu-22.04
  - name: ubuntu-20.04
    driver:
      box: bento/ubuntu-20.04
  - name: centos-8
    driver:
      box: bento/centos-8

suites:
  - name: default
    run_list:
      - recipe[my_cookbook::default]
    attributes:
      nginx:
        port: 8080
  - name: webserver
    run_list:
      - role[webserver]
    verifier:
      inspec_tests:
        - test/integration/webserver

InSpec Tests

Write and run infrastructure compliance tests

rubyยทCommon InSpec resources
# test/integration/default/default_test.rb

# Package installed
describe package('nginx') do
  it { should be_installed }
  its('version') { should match /^1.24/ }
end

# Service running and enabled
describe service('nginx') do
  it { should be_enabled }
  it { should be_running }
end

# Port listening
describe port(80) do
  it { should be_listening }
  its('protocols') { should include 'tcp' }
end

# File exists with correct permissions
describe file('/etc/nginx/nginx.conf') do
  it { should exist }
  its('owner') { should eq 'root' }
  its('group') { should eq 'root' }
  its('mode')  { should cmp '0644' }
  its('content') { should match /worker_processes/ }
end

# Directory exists
describe directory('/var/log/nginx') do
  it { should exist }
  it { should be_directory }
end

# User exists
describe user('deploy') do
  it { should exist }
  its('shell') { should eq '/bin/bash' }
end

# Command output
describe command('nginx -t') do
  its('exit_status') { should eq 0 }
  its('stdout') { should match /syntax is ok/ }
end
bashยทRun InSpec standalone
# Run tests against localhost
inspec exec test/integration/default

# Run against a remote SSH target
inspec exec test/ -t ssh://user@10.0.0.1 -i ~/.ssh/id_rsa

# Run against a Docker container
inspec exec test/ -t docker://container_id

# Run a specific control
inspec exec test/ --controls nginx_running

# Run with a custom attributes file
inspec exec test/ --input-file attributes.yml

# Run a compliance profile from Chef Automate / Supermarket
inspec exec https://github.com/dev-sec/linux-baseline

# Generate a new InSpec profile
inspec init profile my_profile

# Check profile syntax
inspec check test/integration/default

# Output formats
inspec exec test/ --reporter cli json:results.json html:results.html

Policyfiles

Lock cookbook versions and run lists with Policyfiles

bashยทpolicyfile workflow
# Generate a Policyfile
chef generate policyfile Policyfile.rb

# Install / resolve dependencies (creates Policyfile.lock.json)
chef install

# Update a single cookbook in the lock
chef update cookbooks/nginx

# Update all cookbooks
chef update

# Push a policy to Chef Server (policy group = environment analogue)
chef push production Policyfile.rb
chef push staging   Policyfile.rb

# Show policies on the server
chef show-policy my_policy
chef show-policy my_policy production

# List all policy groups
knife policy group list

# Delete a policy
chef delete-policy my_policy
chef delete-policy-group production
rubyยทPolicyfile.rb
# Policyfile.rb โ€” single source of truth for run list + cookbook versions

name    'webserver'

# Where to find cookbooks
default_source :supermarket

# The run list for nodes using this policy
run_list 'recipe[base]', 'recipe[nginx]', 'recipe[app::deploy]'

# Pin specific cookbook versions
cookbook 'nginx',  '~> 4.0'
cookbook 'base',   '= 2.3.1'
cookbook 'app',    path: '.'              # this repo
cookbook 'secrets', git: 'https://github.com/myorg/secrets-cookbook.git', branch: 'main'

# Named run lists (for ad-hoc use)
named_run_list :update_app, 'recipe[app::deploy]'
named_run_list :rotate_certs, 'recipe[base::certs]'