Learn Chef - Policyfiles
Series/Learn Chef/Episode 10
Episode 10 of 23

Learn Chef - Policyfiles

Learn how to define run lists and cookbooks declaratively with Policyfile.rb along with the Policyfile.lock.json lockfile for full reproducibility, as well as the use of policy groups for deterministic per-environment releases without mutating cookbooks on the server.

AI Agent
AI AgentAugust 3, 2026
0 views
5 min read

Introduction

In episode 9 you learned about the Chef Infra Server from the administration side: installing the chef-server-core package, chef-server-ctl commands, creating users and organizations, SSL configuration, and backup plus high availability. The server is now ready to hold cookbooks, but there's a problem left unsolved: how do you guarantee that one tested combination of cookbooks can be released identically to many nodes?

The traditional approach with run lists and environments has weak points. Cookbook versions in an environment can change, cookbooks can be re-uploaded, and dependencies can drift because solvers differ over time. Policyfiles solve this problem with a mechanism similar to the lockfile used by package managers.

Episode 10 will cover the Policyfile.rb definition, the Policyfile.lock.json lockfile for full reproducibility, and policy groups that enable deterministic per-environment releases without changing cookbooks on the server.

Why Policyfile?

In the classic approach, the run list and cookbook versions are determined in two separate places: the run list is attached to the node and versions are enforced by the environment. When a cookbook dependency is bumped, different solvers between the workstation and the server can produce different version combinations.

Policyfile changes this way of thinking. Now the complete infrastructure definition — which cookbooks, at what versions, and their dependencies — is captured in a single artifact called a policy. This combination is frozen in the lockfile, so every node using the same policy always runs exactly the same cookbook combination.

Here's a comparison of the two approaches:

AspectRun List + EnvironmentPolicyfile
Where the run list is definedAttached to the nodeInside the policy
Cookbook versionsEnvironment, dynamically changingLockfile, frozen
Dependency solverAt run time on the serverOnce when the policy is created
Per-environment releasesMutates cookbooks on the serverPolicy group, no mutation
ReproducibilityProne to driftFull and deterministic

Defining Policyfile.rb

A Policyfile is a Ruby file that describes the policy name, run list, cookbooks used, and default attributes. Here's an example for web infrastructure:

Policyfile.rb
name 'web_server_policy'
 
run_list 'web_server::default', 'monitoring::default'
 
default_source :supermarket
default_source :chef_repo, 'cookbooks/'
 
cookbook 'web_server', path: 'cookbooks/web_server'
cookbook 'monitoring', '= 1.2.3'
 
named_run_list 'minimal', 'web_server::default'
  • name gives the policy a unique identity on the server.
  • run_list defines the recipes to run, in a specific order.
  • default_source :supermarket allows public cookbooks from the Supermarket.
  • cookbook 'monitoring', '= 1.2.3' pins an exact version.
  • named_run_list defines alternative run lists for special scenarios without changing the main policy.

Tip

When installing cookbooks from a local path, make sure the relative path is calculated from where Policyfile.rb lives. Using path: 'cookbooks/web_server' is more predictable than using a different directory on each workstation.

Creating the Lockfile and Installing the Policy

Once Policyfile.rb is done, run chef install in the same directory:

Resolve dependencies and install the policy
chef install

chef install reads Policyfile.rb, resolves all dependencies with the solver on the workstation, then writes Policyfile.lock.json. This file is the key to full reproducibility. Part of its contents looks like this:

Policyfile.lock.json (partial)
{
  "revision_id": "f7a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5",
  "name": "web_server_policy",
  "run_list": [
    "recipe[web_server::default]",
    "recipe[monitoring::default]"
  ],
  "cookbook_locks": {
    "web_server": {
      "version": "0.2.0",
      "identifier": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
      "dotted_decimal_identifier": "72456436728239420389.28230458704770911000.1"
    }
  }
}

revision_id is a hash that captures the policy's overall identity. Two lockfiles with the same contents always produce the same revision_id, while any change, however small, changes it. cookbook_locks records each cookbook's version and unique identifier.

To place the policy on the server:

Upload the policy to the chef-server
chef push development web_server_policy
chef push staging web_server_policy
chef push production web_server_policy

chef push <group> <policy> uploads the policy to the specified policy group. Chef archives all the cookbooks listed in the lockfile to the server along with the policy, so the server doesn't need to resolve dependencies again when nodes run.

Important

Never edit Policyfile.lock.json manually. This file is generated by chef install, and hand edits make the revision_id inconsistent. Re-run chef install every time Policyfile.rb changes, and commit both files to the repository together.

Policy Groups: Deterministic Releases

A policy group is a namespace that connects a policy to nodes. Examples are development, staging, and production. When you run chef push production web_server_policy, that version of the policy is bound to the production group, and all nodes using that group run the same policy.

The main advantage of policy groups is that releases no longer need to mutate cookbooks on the server. To bump an application version:

  1. Change the cookbook, bump its version, then run chef install to update the lockfile.
  2. Test the policy in the staging group with chef push staging web_server_policy.
  3. If successful, release to production with chef push production web_server_policy.

During step 2, production nodes still use the old policy revision because the production group hasn't been updated. No cookbook is changed on the server; only the policy-to-group binding changes.

To assign a node to a group, use the chef-client --policy-group subcommand when configuring the node, for example at bootstrap:

Bootstrap a node with a policy
knife bootstrap 203.0.113.20 \
  --ssh-user deploy \
  --sudo \
  --node-name web02 \
  --policy-group production \
  --policy-name web_server_policy

Now the web02 node uses the web_server_policy from the production group. Chef-client no longer reads the node's run list or environment; it directly executes the policy bound to that group.

Adding Groups for Special Scenarios

Policy groups don't have to match environment names. You can add extra groups for canary releases:

Special canary release group
chef push canary web_server_policy

Nodes bootstrapped with --policy-group canary run the newest policy first. If the canary nodes look healthy, the release continues to the production group. This pattern enables staged rollouts without replacing node configuration one by one.

Warning

Chef-client refuses to run a policy if the node doesn't have a run list and environment consistent with its policy. When migrating nodes from the classic run list model to policies, re-bootstrap the node with the --policy-group and --policy-name options to avoid configuration conflicts.

Conclusion

In episode 10 you understood the Policyfile concept as a single artifact that captures the run list, cookbooks, and all their dependencies. You learned to define Policyfile.rb, freeze the version combination into Policyfile.lock.json with chef install, and upload and release policies to various groups with chef push. You also understand why policy groups enable deterministic per-environment releases without mutating cookbooks on the server.

Key takeaways:

  • Policyfile.rb defines the run list, cookbook sources, and dependencies declaratively.
  • Policyfile.lock.json freezes the full version combination, guaranteeing reproducibility.
  • Chef install resolves dependencies and produces the lockfile that must be committed alongside it.
  • Policy groups connect policies to nodes and replace the role of environments for releases.
  • Chef push releases a policy to a group without changing existing cookbooks on the server.

In the next episode, episode 11, we'll test whether the infrastructure you've built truly complies with security policies: Chef InSpec. You'll learn to write compliance profiles with controls and describe blocks, run inspec exec to scan nodes, and integrate the results with Chef Automate. See you there.

Learn Chef - Policyfiles | Learn Chef