What I Am

Life as a Puppet catalogintermediate · standard · comprehensive | model: claude-opus-4-8 | 2026-06-19
I am the catalog
puppet agent -t request, compile & apply me
puppet catalog compile $node build me on the master
--noop simulate convergence, change nothing
facter -p see the facts that shape me
puppet lookup key resolve Hiera data
desired state not a script — a specification

What I Am

I am a compiled, node-specific directed acyclic graph of resources. I describe what your system should look like, never the steps to get there. The agent reads me and converges reality toward my declarations — adding, changing, or leaving alone whatever is needed.

Declarative, not imperative

Order of resources in source is irrelevant; only explicit relationships establish sequence. I am evaluated to a state, not executed line by line.

Idempotent by design

Applying me twice produces no second change. Each resource is checked against current state and only acted on if it diverges.

Note I am unique per agent. The same manifests compile differently for each node because facts and classification differ.

Resources, Types & Providers

Resources are my atoms: a type, a title, and attributes describing desired state.

package { 'nginx':
  ensure => installed,
}
service { 'nginx':
  ensure  => running,
  enable  => true,
  require => Package['nginx'],
}
type
Abstract model of a manageable thing (package, service, file, user).
provider
Platform-specific implementation (apt vs yum, systemd vs init) selected from facts.
puppet resource service nginx
Introspect live state as a resource declaration.
puppet describe package
List attributes and available providers for a type.
Tip The provider abstraction is why I am portable: declare ensure => running and let the agent pick systemd or upstart.

How I Come to Exist — The Compilation Pipeline

StageWhat happens
1. RequestAgent submits its certname and plugin-synced facts to the master.
2. ClassifyNode classifier / site.pp / ENC assigns classes to the node.
3. Inject factsFacts become top-scope $facts variables available to manifests.
4. EvaluatePuppet DSL is parsed and evaluated; Hiera lookups resolve; classes & defines expand into resources.
5. Build graphRelationships and containment edges produce a DAG — that is me.
6. SerializeI am sent to the agent as JSON and cached locally.
Warning Compilation is the only phase with master-side logic. Once serialized I contain no conditionals — every branch is already resolved for this node.

Facter & the Trust Boundary

Facter runs on the agent and reports system inventory (OS, IP, memory, custom facts). I am shaped by these values during compilation.

$facts['os']['family']
Structured fact access inside manifests.
facter -p networking.ip
Query a single fact including Puppet's custom facts.
$trusted['certname']
Validated facts derived from the agent's signed certificate.
Warning Ordinary facts are agent-supplied and spoofable. Only $trusted data, extracted from the cert, is safe for security-sensitive classification decisions.

Relationships, Ordering & Containment

Because I am declarative, sequence must be stated explicitly. Metaparameters and chaining arrows define my edges.

require => Package['x']
This resource applies after the named one.
before => Service['x']
This resource applies before the named one.
notify => Service['x']
Order plus refresh signal when this changes.
subscribe => File['conf']
Order plus refresh when the target changes.
File['a'] -> Service['b']
Chaining arrow: ordering edge.
File['a'] ~> Service['b']
Chaining arrow with notification (refresh).
Note Containment: a class contains its resources, so ordering a class orders everything within it. A cycle in my graph is fatal — the agent refuses to apply.

Data Separation — Hiera

Code declares structure; Hiera supplies the values. Lookups resolve hierarchically by node-specific facts during compilation, keeping me free of hardcoded data.

# hiera.yaml hierarchy
hierarchy:
  - name: "Per-node"
    path: "nodes/%{trusted.certname}.yaml"
  - name: "Per-OS"
    path: "os/%{facts.os.family}.yaml"
  - name: "Common"
    path: "common.yaml"
lookup('ntp::servers')
Explicit data lookup with merge behaviour control.
class profile::ntp ($servers) {}
Automatic parameter lookup binds profile::ntp::servers from Hiera.

Module Structure & Roles/Profiles

Module layout

mymod/
  manifests/init.pp
  templates/conf.epp
  files/static.txt
  lib/facter/
  data/
  hiera.yaml

Roles & Profiles

A profile wraps and configures component modules for one technology. A role composes profiles to describe a machine's business purpose. Each node gets exactly one role.

class role::webserver {
  include profile::base
  include profile::nginx
  include profile::monitoring
}
Tip Keep site.pp trivial: classify nodes to a single role and let composition do the rest.

Exported Resources & PuppetDB

Resources declared on one node can be collected by another, enabling cross-node orchestration like load-balancer pools and monitoring registration. PuppetDB stores exported resources, facts, and my reports.

@@nagios_host { $hostname: ... }
Export a resource for other nodes to collect.