Chef Guide — Infrastructure as Code with Cookbooks and Recipes
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
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_baseWriting 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'
endOutput: 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']
)
endOutput: 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
endOutput: 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:
- Ohai collects node attributes
- Chef Infra Client connects to the Chef Server
- Node object is loaded with current run list
- Cookbooks are downloaded from the server
- Converge — resources are compared to desired state and actions taken
- Node object is saved back with updated attributes
- 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 --daemonizeSecurity 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
endDodaTech 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
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.
Imperative thinking: Chef is declarative. Don’t write
if not installed, install. Just declarepackage 'nginx'withaction :install— Chef handles idempotency.Hard-coding sensitive data: Never put passwords, API keys, or certificates in recipes. Use encrypted data bags or Chef Vault for secrets.
Notifying services outside the cookbook:
notifiesonly works within the same Chef run. If a resource is in a recipe that runs later, the notification may be missed.Over-using execute resources:
executeruns arbitrary shell commands without idempotency guarantees. Prefer specialized resource types (package, service, file, template).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
- What is the difference between Chef Server and Chef Workstation?
- How do data bags differ from node attributes?
- What does
--why-rundo in Chef? - Why should you use resource types instead of
execute? - How do you make a recipe platform-agnostic?
Answers:
- 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.
- 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.
--why-runperforms a dry run — it shows what changes Chef would make without applying them. Useful for auditing and planning.- Resource types (package, service, file) are inherently idempotent and handle platform differences.
executeruns arbitrary commands without these guarantees. - Use Ohai attributes like
node['platform_family']for conditional logic, and use resource-level guards likeonly_ifandnot_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
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.txtWhat’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