Omnibus Installer

% curl -L | sudo bash -s -- -v 12.16.42

% chef-client -v
Chef: 12.16.42

# Use a stable version of Ruby as part of the omnibus installer (Optional)
# echo 'export PATH="/opt/chef/embedded/bin:$PATH"' >> ~/.bash_profile
# source ~/.bash_profile
% which ruby

chef-zero vs. chef-solo

The command chef-solo has been deprecated. Use the command chef-client -z, instead.

$ mkdir chef-repo
$ cd chef-repo

# Create a cookbook
% knife cookbook create httpd -o cookbooks

# Define a recipe
$ vi cookbooks/httpd/recipes/default.rb
package 'httpd' do
  action :install

service 'httpd' do
supports :status => true, :restart => true, :reload => true
  action [:enable, :start]

# Add the recipe to the run_list
% vi nodes/localhost.json
  "run_list": [

# Execute chef-client in local mode
% sudo chef-client -z -N localhost -j nodes/localhost.json

The option -z means that the client runs in local mode. The client will load the cookbooks on localhost (i.e. the same machine). The chef-zero server, which serves the local chef-repo, will be launching in memory during applying cookbooks.


The command knife is useful to manage nodes even in local mode.

I would rather create the configuration file chef-repo/knife.rb so as not to specify the same options every time.

$ cd chef-repo
$ cat knife.rb
local_mode true
chef_repo_path File.expand_path('../' , __FILE__)

knife[:ssh_attribute] = 'ipaddress'
knife[:sudo] = true

The nodes/*.json files must be created. Each file contains each target node information.

# create nodes/<hostname>.json
$ knife node create <hostname> --disable-editing
  "name": "<hostname>"

You can specify another node name with the real IP address.

$ vi nodes/<node-name>.json
  "name": "<node-name>",
  "normal": {
    "ipaddress": ""

Assume that the following nodes exist.

$ tree nodes
├── development-ap.json
└── production-ap1.json
└── production-ap2.json

The knife search command shows the nodes that match a search query.

$ knife search node "name:*"
Node Name:   development-ap
Node Name:   production-ap1
Node Name:   production-ap2

The knife environment command can manage environments/*.json

$ knife environment create development --disable-editing
$ knife environment create production --disable-editing
$ tree environments
├── development.json
└── production.json

Specifying the chef_environment of each node is helpful to pick up nodes with environment names.

$ knife node enviroment set development-ap development
$ cat nodes/development-ap.json
  "name": "development-ap",
  "chef_environment": "development",
$ knife node enviroment set production-ap1 production
$ knife node enviroment set production-ap2 production

$ knife search node "chef_environment:production"
Node Name:   production-ap1
Node Name:   production-ap2

The knife ssh command can execute any command on each node via. SSH.

$ knife ssh "name:production-*" "sudo systemctl is-active httpd"
production-ap1 active
production-ap2 active

The knife exec command can execute a ruby script under the knife configuration.

$ knife exec -E 'nodes.all {|n| p n }'
$ cat chec_chef_version.rb
nodes.all do |n|
  system "ssh #{n['ipaddress']} 'sudo chef-client -v'"
$ knife exec -z check_chef_version.rb


Checking .rb .erb Syntax

$ ruby -c path/to/cookbook/recipe/default.rb
$ erb -x path/to/cookbook/templates/default/httpd.conf.erb | ruby -c

Converge Nodes via. SSH Port Forwarding

$ cat converge.rb
nodes.all do |n|
  system "ssh -R8889: #{n['ipaddress']}" <<
      " sudo chef-client -S -N #{}"
$ knife exec converge.rb
  1. Launch a chef-zero server on the local chef-repo.
  2. Connect to each node with SSH port forwarding (local) 8889 -> (node)
  3. Execute chef-client in server mode on each node to connect the local chef-zero server.

Make sure that the chef-zero server launches on the local chef-repo (i.e. your working directory). Those who can access the remote nodes can also communicate with your chef-repo via the bound port. In other words, any OS users on the nodes can send arbitrary TCP/IP packets to the working machine.