A Chef cookbook lists `depends` entries in its metadata.rb. How do those dependency versions get resolved and pinned for a node, and what did Policyfiles change compared with Berkshelf plus environment version constraints?
answer
- constraints in metadata.rb are only the input
- who solves, and when?
- the lock stopped at the workstation
- one artefact: run list plus every version
- promote the identical lock, do not re-solve
basics
~20 sBerkshelf resolves the metadata.rb constraints into a Berksfile.lock and uploads the cookbooks, while the Chef Infra Server pins versions per environment and expands the run list at run time. Policyfiles replace both with one artefact that locks the run list and every cookbook version together.
solid answer
~50 sWith the older model, a cookbook's `metadata.rb` declares `depends 'apt', '~> 7.0'`, Berkshelf resolves the full graph against those constraints into a `Berksfile.lock`, and `berks upload` pushes the resolved cookbooks to the Chef Infra Server. The node's run list lives on the node, in a role, or in an environment, and the server does its own dependency solve *at run time*, bounded by whatever `cookbook_versions` the environment pins. That is the weak point: the lock file you tested with is a workstation-side artefact, while the version the node gets is whatever the server's solver picks that day. **Policyfiles** collapse the whole thing into one unit. A `Policyfile.rb` declares the run list and the cookbook sources; `chef install` solves the graph once and writes `Policyfile.lock.json` with an exact version and identifier for every cookbook; `chef push <policy_group>` publishes that immutable lock, and nodes in that policy group run precisely it. Promotion becomes pushing the same lock to the next group.
code
ruby · 11 linesname 'app_server'
default_source :supermarket
default_source :chef_repo, '../cookbooks'
run_list 'app::default', 'monitoring::agent'
cookbook 'app', path: '../cookbooks/app'
cookbook 'internal_base',
git: 'https://git.example.com/cookbooks/internal_base.git',
tag: 'v1.2.0'go deeper
Know that a cookbook declares its dependencies in metadata.rb and that a resolver turns those constraints into concrete versions. You are not expected to have run a migration.
Explain the two-place resolution in the older model — Berkshelf on the workstation, the Chef Infra Server at run time — and what a Policyfile.lock.json records instead.
Show you can reason about reproducibility: name the drift that unpinned transitive dependencies cause, and describe promoting one lock through policy groups rather than re-resolving per environment.
Own the migration decision for an inherited estate: sequencing per-node cutover, what replaces environment attributes, and the review process that makes a lock-file diff the moment dependency upgrades get scrutinised.
## The dependency declaration Every cookbook carries a `metadata.rb` that names it, versions it, and declares what it needs: ```ruby name 'app' version '2.4.1' depends 'apt', '~> 7.0' depends 'internal_base', '>= 1.2' ``` Those constraints are the input to a solver. What differs between the two models is *who runs the solver, when, and whether the answer is recorded*. ## The Berkshelf-and-environments model Berkshelf reads a `Berksfile` that points at cookbook sources — the public Chef Supermarket, an internal Supermarket, a Git repository, a local path — resolves the transitive graph against the `depends` constraints, and writes a `Berksfile.lock` recording the exact versions it chose. `berks install` resolves; `berks upload` pushes the resolved set to the Chef Infra Server. But the lock stops at the workstation. What the node actually runs is decided elsewhere: - The node has a **run list** (`recipe[app::default]`), possibly via a **role**. - The node belongs to an **environment**, which can carry `cookbook_versions` constraints such as `'app' => '= 2.4.1'`. - At the start of the run, the Chef Infra Server expands the run list and solves dependencies against everything uploaded, bounded by the environment's pins. So the resolution happens twice, in two different places, with two different sets of inputs. If a colleague uploads `internal_base 1.3` between your test run and production's next converge, and the environment does not pin it, production gets 1.3 and staging tested 1.2. Pinning every cookbook in every environment is possible, and teams did it, but it is a hand-maintained list that has to be updated across environments in lockstep — the classic "we pinned the app cookbook but not its dependency" incident. There is a second sharp edge: environments pin cookbook versions, but they also carry attributes, and roles carry both run lists and attributes. Working out why a node got a particular value means tracing node, role, environment and cookbook attributes through the precedence table, and the version story is tangled up in the same objects. ## The Policyfile model A Policyfile replaces run list plus environment pins plus Berksfile with a single source file: ```ruby name 'app_server' default_source :supermarket default_source :chef_repo, '../cookbooks' run_list 'app::default', 'monitoring::agent' cookbook 'app', path: '../cookbooks/app' cookbook 'internal_base', git: 'https://git.example.com/cookbooks/internal_base.git', tag: 'v1.2.0' ``` `chef install` solves the graph once and writes `Policyfile.lock.json`, which records the run list plus an exact version *and a content identifier* for every cookbook in the graph. That lock is the artefact you test, review and promote. `chef push staging` publishes it to the `staging` **policy group**; nodes associated with that policy name and group fetch exactly those cookbook identifiers. No run-time solve, no environment pin list, no chance of a node quietly picking up a newer transitive dependency. Promotion is then a deliberate act: `chef push production` pushes the identical lock that staging has been running. `chef update` is how you deliberately re-solve to pick up newer versions, which is the point — upgrades become a reviewed diff of the lock file rather than a side effect of someone uploading a cookbook. ## What this maps onto The shape is familiar from application dependency management: resolve once, commit the lock, deploy the lock. Berkshelf had the lock but the server ignored it; Policyfiles make the lock the thing that ships. If you have used a language package manager's lock file, you already know why the second model is calmer. ## Practical notes for the interview - Policyfiles and the older model are **mutually exclusive for a given node** — a node is either policy-managed or run-list-and-environment managed. Migration is per-node, and mixed fleets during migration are normal. - Policy groups largely take over the job environments were doing for versioning, though environments still exist for nodes not yet migrated. - `include_policy` lets one Policyfile pull in another's lock, which is how shared base policies are expressed without copy-pasting run lists. - Cookbook version constraints in `metadata.rb` still matter under Policyfiles — they are the input to `chef install`'s solve. What changes is that the answer gets frozen. ## The judgment an interviewer is testing The question behind the question is whether you understand that *"it worked in staging"* only means something when staging and production ran the same bytes. Being able to explain that the older Chef model had two independent resolution points, and that Policyfiles exist to remove one of them, shows you have thought about reproducibility rather than just typed `berks upload`.
- What breaks when an environment pins the top-level cookbook but not its transitive dependencies?The server's run-time solve is free to pick any version of the unpinned dependency that satisfies the constraints, so a colleague uploading a newer one changes production's behaviour without anyone touching the pinned cookbook or the run list. It is the exact failure Policyfiles remove by locking the whole resolved graph, dependencies included, into one published artefact.
- How do you promote a tested configuration from staging to production under Policyfiles?Push the same lock to the next policy group — `chef push production` with the `Policyfile.lock.json` that staging has been running. Nothing re-solves, so production runs identical cookbook identifiers. Picking up newer dependencies is a separate, deliberate step with `chef update`, which rewrites the lock and goes back through the same test-then-promote path.
- Can a fleet run Policyfile-managed and run-list-managed nodes at the same time?Yes — the choice is per node, which is what makes migration feasible. A node associated with a policy name and group ignores environment cookbook pins and role run lists; the rest keep working as before. Mixed estates are normal mid-migration, but they do mean two mental models in play, so it is worth finishing the migration rather than living there.
saying these in an interview costs you the question
- Believes Berksfile.lock is what pins the versions a node runs
- Thinks metadata.rb constraints alone guarantee reproducible runs
- Assumes environments pin transitive dependencies automatically
- Says Policyfiles are just a renamed Berksfile
- Treats berks upload as a deployment rather than a publish