Developing and testing Chef cookbooks locally on a Windows workstation requires three core components: the Chef Development Kit (ChefDK) (since superseded by Chef Workstation), a local hypervisor (Oracle VirtualBox), and HashiCorp Vagrant so Test Kitchen can spin up disposable virtual machines to converge and verify recipes.

This guide walks through installing the toolchain on Windows, generating a new cookbook (my_first_cookbook / tomcat), configuring .kitchen.yml, converging a Linux VM, and running automated integration tests with Serverspec.

Historical Note: In newer Chef releases, ChefDK has been replaced by Chef Workstation, and InSpec is the default Test Kitchen verifier instead of Busser/Serverspec. The workflow (chef generate cookbook, kitchen create, kitchen converge, kitchen verify, kitchen destroy) remains identical.


Step 1: Install ChefDK, Vagrant, and VirtualBox

1. Install ChefDK (or Chef Workstation)

Download the Windows .msi installer from Chef and run it using the default installation options. Once completed, open a new PowerShell window so the ChefDK tools (chef, kitchen, berks, knife) are available in your PATH.

Download File

Download File

Verify the installation in PowerShell:

chef --version
kitchen --version

2. Install HashiCorp Vagrant

Download and install the Windows MSI package from Vagrant Downloads, then restart PowerShell if prompted.

3. Install Oracle VirtualBox

Download and install the Windows host installer from VirtualBox Downloads. Make sure hardware virtualization (Intel VT-x or AMD-V) is enabled in your system BIOS/UEFI.


Step 2: Generate a New Cookbook

Use chef generate cookbook to scaffold a standard Chef cookbook structure (including metadata.rb, Berksfile, .kitchen.yml, recipes/default.rb, ChefSpec unit tests under spec/, and integration tests under test/):

chef generate cookbook my_first_cookbook

Example Output

PS C:\Users\zubair.ahmed\centos_vagrant> chef generate cookbook my_first_cookbook
Installing Cookbook Gems:
Compiling Cookbooks...
Recipe: code_generator::cookbook
  * directory[C:/Users/zubair.ahmed/centos_vagrant/my_first_cookbook] action create
    - create new directory C:/Users/zubair.ahmed/centos_vagrant/my_first_cookbook
  * template[C:/Users/zubair.ahmed/centos_vagrant/my_first_cookbook/metadata.rb] action create_if_missing
    - create new file C:/Users/zubair.ahmed/centos_vagrant/my_first_cookbook/metadata.rb
  * template[C:/Users/zubair.ahmed/centos_vagrant/my_first_cookbook/README.md] action create_if_missing
    - create new file C:/Users/zubair.ahmed/centos_vagrant/my_first_cookbook/README.md
  * cookbook_file[C:/Users/zubair.ahmed/centos_vagrant/my_first_cookbook/chefignore] action create
    - create new file C:/Users/zubair.ahmed/centos_vagrant/my_first_cookbook/chefignore
  * cookbook_file[C:/Users/zubair.ahmed/centos_vagrant/my_first_cookbook/Berksfile] action create_if_missing
    - create new file C:/Users/zubair.ahmed/centos_vagrant/my_first_cookbook/Berksfile
  * template[C:/Users/zubair.ahmed/centos_vagrant/my_first_cookbook/.kitchen.yml] action create_if_missing
    - create new file C:/Users/zubair.ahmed/centos_vagrant/my_first_cookbook/.kitchen.yml
  * directory[C:/Users/zubair.ahmed/centos_vagrant/my_first_cookbook/test/integration/default/serverspec] action create
  * directory[C:/Users/zubair.ahmed/centos_vagrant/my_first_cookbook/test/integration/helpers/serverspec] action create
  * cookbook_file[C:/Users/zubair.ahmed/centos_vagrant/my_first_cookbook/test/integration/helpers/serverspec/spec_helper.rb] action create_if_missing
  * template[C:/Users/zubair.ahmed/centos_vagrant/my_first_cookbook/test/integration/default/serverspec/default_spec.rb] action create_if_missing
  * directory[C:/Users/zubair.ahmed/centos_vagrant/my_first_cookbook/spec/unit/recipes] action create
  * cookbook_file[C:/Users/zubair.ahmed/centos_vagrant/my_first_cookbook/spec/spec_helper.rb] action create_if_missing
  * template[C:/Users/zubair.ahmed/centos_vagrant/my_first_cookbook/spec/unit/recipes/default_spec.rb] action create_if_missing
  * directory[C:/Users/zubair.ahmed/centos_vagrant/my_first_cookbook/recipes] action create
  * template[C:/Users/zubair.ahmed/centos_vagrant/my_first_cookbook/recipes/default.rb] action create_if_missing

Step 3: Configure .kitchen.yml and Create the Test VM

Open .kitchen.yml inside your cookbook directory. It defines four sections:

  • driver: How VMs are provisioned (vagrant backed by VirtualBox).
  • provisioner: How Chef runs inside the guest (chef_zero).
  • platforms: Target operating system images (Vagrant Bento boxes).
  • suites: Test suites, run lists, and node attributes.
---
driver:
  name: vagrant

provisioner:
  name: chef_zero

# Uncomment the following verifier to leverage Inspec instead of Busser (the
# default verifier)
# verifier:
#   name: inspec

platforms:
  - name: centos-6.5

suites:
  - name: default
    run_list:
      - recipe[my_first_cookbook::default]
    attributes:

List and Create the Test Kitchen Instance

List the configured instances from .kitchen.yml:

kitchen list
PS C:\Users\zubair.ahmed\centos_vagrant\my_first_cookbook> kitchen.bat list
Instance           Driver   Provisioner  Verifier  Transport  Last Action
default-centos-65  Vagrant  ChefZero     Busser    Ssh        <Not Created>

Provision the Vagrant VM with kitchen create:

kitchen create default-centos-65
PS C:\Users\zubair.ahmed\centos_vagrant\my_first_cookbook> kitchen.bat create default-centos-65
-----> Starting Kitchen (v1.7.3)
-----> Creating <default-centos-65>...
       Bringing machine 'default' up with 'virtualbox' provider...
       ==> default: Importing base box 'bento/centos-6.5'...
       ==> default: Matching MAC address for NAT networking...
       ==> default: Setting the name of the VM: kitchen-my_first_cookbook-default-centos-65_default_1465204398970_64998
       ==> default: Forwarding ports...
           default: 22 (guest) => 2222 (host) (adapter 1)
       ==> default: Booting VM...
       ==> default: Waiting for machine to boot. This may take a few minutes...
           default: SSH address: 127.0.0.1:2222
           default: SSH username: vagrant
           default: SSH auth method: private key
       ==> default: Machine booted and ready!
       [SSH] Established
       Vagrant instance <default-centos-65> created.
       Finished creating <default-centos-65> (1m58.85s).
-----> Kitchen is finished. (2m7.25s)

Download File


Step 4: Write the Recipe and Integration Tests

Edit recipes/default.rb to install OpenJDK, create a dedicated tomcat system user and group, download and extract Apache Tomcat into /opt/tomcat, set permissions, and start the Tomcat service:

package 'java-1.7.0-openjdk' do
  action :install
end

# Create tomcat group
group 'tomcat' do
  action :create
end

# Create tomcat user
user 'tomcat' do
  group 'tomcat'
  home '/opt/tomcat'
  shell '/bin/nologin'
  uid '1234'
  action :create
end

# Create installation directory
directory '/opt/tomcat' do
  owner 'tomcat'
  group 'tomcat'
  mode '0755'
  action :create
end

# Download Apache Tomcat archive
remote_file '/tmp/apache-tomcat-8.0.33.tar.gz' do
  source 'https://archive.apache.org/dist/tomcat/tomcat-8/v8.0.33/bin/apache-tomcat-8.0.33.tar.gz'
end

# Extract Apache Tomcat if not already extracted
execute 'tar xvzf /tmp/apache-tomcat-8.0.33.tar.gz -C /opt/tomcat/ --strip-components=1' do
  not_if { ::File.exist?('/opt/tomcat/conf/server.xml') }
end

# Ensure tomcat user owns extracted files
execute 'chown -R tomcat:tomcat /opt/tomcat'

directory '/opt/tomcat/conf' do
  mode '0070'
end

execute '/opt/tomcat/bin/startup.sh' do
  not_if 'pgrep -f org.apache.catalina.startup.Bootstrap'
end

Next, update test/integration/default/serverspec/default_spec.rb to verify that Tomcat responds on port 8080 and that the directories, user, group, and file modes match expectations:

require 'spec_helper'

describe 'tomcat::default' do
  describe command('curl --noproxy 127.0.0.1 http://127.0.0.1:8080') do
    its(:stdout) { should match(/tomcat/i) }
  end

  describe file('/opt/tomcat') do
    it { should exist }
    it { should be_directory }
  end

  describe group('tomcat') do
    it { should exist }
  end

  describe user('tomcat') do
    it { should exist }
    it { should belong_to_group 'tomcat' }
    it { should have_home_directory '/opt/tomcat' }
    it { should have_login_shell '/bin/nologin' }
  end

  describe file('/opt/tomcat/conf') do
    it { should exist }
    it { should be_mode 70 }
  end
end

Step 5: Converge the VM (kitchen converge)

Run kitchen converge to install Chef Client inside the guest VM (if not already present), upload the cookbook via Berkshelf, and apply the recipe:

kitchen.bat converge default-centos-65
PS C:\Users\zubair.ahmed\centos_vagrant\tomcat> kitchen.bat converge default-centos-65
-----> Starting Kitchen (v1.7.3)
-----> Converging <default-centos-65>...
       Preparing files for transfer
       Preparing dna.json
       Resolving cookbook dependencies with Berkshelf 4.3.2...
       Removing non-cookbook files before transfer
       Preparing solo.rb
-----> Chef Omnibus installation detected (install only if missing)
       Transferring files to <default-centos-65>
       Starting Chef Client, version 12.11.18
       resolving cookbooks for run list: ["tomcat::default"]
       Synchronizing Cookbooks:
         - tomcat (0.1.0)
       Installing Cookbook Gems:
       Compiling Cookbooks...
       Converging 9 resources
       Recipe: tomcat::default
         * yum_package[java-1.7.0-openjdk] action install
           - install version 1.7.0.101-2.6.6.4.el6_8 of package java-1.7.0-openjdk
         * group[tomcat] action create
           - create group tomcat
         * user[tomcat] action create
           - create user tomcat
         * directory[/opt/tomcat] action create (up to date)
         * remote_file[/tmp/apache-tomcat-8.0.33.tar.gz] action create
           - create new file /tmp/apache-tomcat-8.0.33.tar.gz
         * execute[tar xvzf /tmp/apache-tomcat-8.0.33.tar.gz -C /opt/tomcat/ --strip-components=1] action run
         * execute[chown -R tomcat:tomcat /opt/tomcat] action run
         * directory[/opt/tomcat/conf] action create
           - change mode from '0755' to '070'
         * execute[/opt/tomcat/bin/startup.sh] action run

       Chef Client finished, 8/9 resources updated in 53 seconds
       Finished converging <default-centos-65> (0m56.68s).
-----> Kitchen is finished. (1m2.05s)

Download File


Step 6: Run Integration Tests (kitchen verify)

Execute kitchen verify to transfer the Serverspec test suite to the VM and validate all assertions:

kitchen.bat verify default-centos-65
       tomcat::default
         Command "curl --noproxy 127.0.0.1 http://127.0.0.1:8080"
           stdout
             should match /tomcat/
         File "/opt/tomcat"
           should exist
           should be directory
         Group "tomcat"
           should exist
         User "tomcat"
           should exist
           should belong to group "tomcat"
           should have home directory "/opt/tomcat"
           should have login shell "/bin/nologin"
         File "/opt/tomcat/conf"
           should exist
           should be mode 70

       Finished in 1.92 seconds (files took 0.38732 seconds to load)
       10 examples, 0 failures

       Finished verifying <default-centos-65> (0m26.23s).
-----> Kitchen is finished. (0m31.66s)

Download File

When all 10 examples, 0 failures pass, you can tear down the test VM with kitchen destroy (or run the full create-converge-verify-destroy lifecycle in one command with kitchen test).

References