Learn Puppet - Catalog & Resource Relationships
Episode 8 of 23

Learn Puppet - Catalog & Resource Relationships

In this episode we bring manifests and data together into a catalog: ordering resources with before, require, notify, subscribe, chaining arrows, and dissecting the most common catalog errors.

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

Introduction

In episode 7 you learned to separate data into Hiera and fetch it with lookup. Now it's time for the unification: manifests and data are compiled by the server into a single document called a catalog. In episode 8 we learn how the catalog is built, how resource ordering is controlled with before, require, notify, subscribe, and chaining arrows, plus the catalog errors that most often fail a run.

A catalog is the final output of compilation — a ready-made list of resources with all their parameters, ready to be executed by the agent. There's no more if or case in it, only concrete resources.

What Is a Catalog

The Puppet cycle is: the agent sends facts to the server, the server compiles a catalog from manifests and Hiera data, then sends the catalog back to the agent. The agent compares the desired state in the catalog with the node's actual state, then corrects the differences.

Compile and inspect a catalog
puppet apply --noop manifests/site.pp
puppet apply --noop --verbose manifests/site.pp

puppet apply --noop is the fastest way to test manifests on a local machine without actually changing the system. With --verbose, you see every resource that would be checked.

Because a catalog is compiled data, the declaration order in the manifest isn't always the same as the execution order. Relationships between resources are managed explicitly, and that's the core topic of this episode.

before and require

Puppet doesn't guarantee execution order between resources without a clear relationship. The before and require metaparameters build forward and backward dependencies:

before and require
package { 'nginx':
  ensure => installed,
  before => File['/etc/nginx/conf.d/app.conf'],
}
 
file { '/etc/nginx/conf.d/app.conf':
  ensure  => file,
  content => template('profile/web/nginx.conf.erb'),
  require => Package['nginx'],
}
 
service { 'nginx':
  ensure => running,
  enable => true,
}

before => File[...] on the package means: the package must finish before the file is managed. require => Package['nginx'] on the file states the same relationship from the opposite direction — using both for the same relationship is redundant.

Tip

Use one, not both, for a relationship pair. require is easier to read because the dependency is stated on the resource that needs another resource. before is practical when you want to state ordering without changing the target resource.

notify and subscribe

The before and require relationships only handle ordering. To trigger additional actions — for example, restarting a service after a config change — use notify and subscribe. These are event-driven relationships: if the notified resource changes, the receiving resource reacts too.

notify and subscribe
file { '/etc/nginx/conf.d/app.conf':
  ensure  => file,
  content => template('profile/web/nginx.conf.erb'),
  notify  => Service['nginx'],
}
 
service { 'nginx':
  ensure => running,
  enable => true,
  subscribe => File['/etc/nginx/conf.d/app.conf'],
}

notify => Service['nginx'] on the file and subscribe => File[...] on the service are two directions of the same relationship. When the file content changes, subscribe makes the service receive the event and restart. If the content doesn't change, there's no restart — this is what makes Puppet efficient.

Important

notify and subscribe only trigger an action when the source resource actually changes state. No restart happens on runs where nothing changed, so idempotency stays intact.

Chaining with Arrows

Besides metaparameters, Puppet provides chaining arrows for chains of relationships. The -> arrow means "must finish first", while ~> means "notify when it changes":

Chaining with arrows
package { 'nginx': ensure => installed }
-> file { '/etc/nginx/conf.d/app.conf':
    ensure  => file,
    content => template('profile/web/nginx.conf.erb'),
  }
~> service { 'nginx':
    ensure => running,
    enable => true,
  }

The chain above is equivalent to the earlier combination of require, before, notify, and subscribe. Chaining is great for linear sequences of steps: install the package, write the config, then run the service.

Relationship formMeaning
before and requireExecution order
notify and subscribeOrder plus event (restart/reload)
->Shorthand for before/require relationships
~>Shorthand for notify/subscribe relationships

Warning

Don't create circular chains — for example A needs B, B needs C, and C needs A. Puppet will detect the dependency cycle at compilation and reject the catalog with an error.

How the Server Compiles a Catalog

When an agent requests a catalog, the server runs these steps:

  1. Determine the node and the classes classified for that node.
  2. Fetch the latest facts sent by the agent.
  3. Evaluate the manifests with the node environment's Hiera data.
  4. Assemble all resources into a graph with dependency relationships.
  5. Run validation and send the compiled catalog back to the agent.

All values to be decrypted from hiera-eyaml are decrypted at step 3 — the agent only receives the final result, never the keys.

Compile a catalog for a specific node
puppet catalog compile --environment production web01.example.com
puppet node clean web01.example.com

Note

Because the server does the compiling, the agent only needs to be smart enough to apply the catalog without understanding Ruby. This also means compilation errors appear on the server side and never end up changing a node.

Common Catalog Errors

A compilation error fails the run before the catalog is ever sent. The most common ones:

Duplicate declaration — two resources with the same type and title in one scope:

Trigger of a duplicate declaration error
file { '/etc/motd':
  ensure  => file,
  content => 'pesan pertama',
}
 
file { '/etc/motd':
  ensure => present,
}

Puppet identifies resources by the type/title pair. Declaring File['/etc/motd'] twice raises the Duplicate declaration error. The solution is always to write one declaration, then refer to that resource by title in relationships.

Class not found — happens when include uses a class name that doesn't exist in the modulepath, or the module isn't installed yet:

Install a module from the Forge
puppet module install puppetlabs-nginx
puppet module list

Undefined variable — a reference to a variable that hasn't been set produces Undefined variable. Check that fact key spelling, for example $facts['os']['family'], is correct.

Syntax error — a missing semicolon at the end of a resource declaration, or unbalanced parentheses. Puppet's error messages usually name the exact line number in the manifest.

Tip

Get into the habit of running puppet parser validate site.pp and puppet apply --noop before pushing to all nodes. Compilation errors are far cheaper to fix while they're still in a single file.

Conclusion

A catalog is the bridge between intent and realization — and the relationships between resources are what make it deterministic.

  • A catalog is the compiled result of manifests plus Hiera data, ready for the agent to run.
  • before and require handle ordering, notify and subscribe add events like service restarts.
  • The -> and ~> chaining arrows write relationship chains linearly and readably.
  • The server compiles and decrypts data, the agent only receives the finished catalog.
  • Common catalog errors are duplicate declarations, missing classes, undefined variables, and syntax errors.

In episode 9, you'll add the brains behind the scenes: PuppetDB, the database that stores facts, catalogs, and per-run reports, enables cross-node queries, and powers exported resources — resources born on one node and collected on another to drive monitoring and load balancers. See you there!

Learn Puppet - Catalog & Resource Relationships | Learn Puppet