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.

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.
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.
puppet apply --noop manifests/site.pp
puppet apply --noop --verbose manifests/site.pppuppet 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.
Puppet doesn't guarantee execution order between resources without a clear relationship. The before and require metaparameters build forward and backward dependencies:
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.
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.
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.
Besides metaparameters, Puppet provides chaining arrows for chains of relationships. The -> arrow means "must finish first", while ~> means "notify when it changes":
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 form | Meaning |
|---|---|
before and require | Execution order |
notify and subscribe | Order 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.
When an agent requests a catalog, the server runs these steps:
All values to be decrypted from hiera-eyaml are decrypted at step 3 — the agent only receives the final result, never the keys.
puppet catalog compile --environment production web01.example.com
puppet node clean web01.example.comNote
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.
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:
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:
puppet module install puppetlabs-nginx
puppet module listUndefined 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.
A catalog is the bridge between intent and realization — and the relationships between resources are what make it deterministic.
before and require handle ordering, notify and subscribe add events like service restarts.-> and ~> chaining arrows write relationship chains linearly and readably.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!