What I Am

Life as a Puppet catalogintermediate · standard · comprehensive | model: claude-opus-4-8 | 2026-06-21
Quick ref
puppet agent -t trigger a run: request catalog, apply, report
--noop simulate convergence, change nothing
puppet catalog compile NODE compile my graph for a node
facter -p show facts the master will trust
puppet resource service nginx introspect live state as a resource
puppet lookup KEY --node N resolve Hiera data for a node

What I Am

A specification, not a script

I am a compiled, node-specific graph of resources describing desired state. I declare what must be true, never the steps to get there — the agent decides actions by comparing me to reality.

Consequences of the declarative model

Order is not implied by file position — it must be stated explicitly. Convergence is automatic and idempotent: applying me repeatedly yields the same end state with no extra changes.

Note I am built fresh for one agent at one moment from that agent's facts plus your code and data; two nodes never share the same compiled catalog.

Resource Types & Provider Abstraction

package { 'nginx': ensure => installed }
A resource: a type, a title, and attributes describing desired state.
service { 'nginx': ensure => running, enable => true }
Same type system; the title must be unique per type within me.
provider => 'systemd'
Providers translate abstract state into platform commands (apt, yum, systemd, launchd).
puppet describe service
List a type's attributes and the providers available for it.
Tip Facter auto-selects a suitable provider per platform, so the same manifest converges identically on Debian and RHEL without conditionals.

Manifests & Module Structure

module/
├── manifests/
│   ├── init.pp        # class module { }
│   └── config.pp      # class module::config { }
├── templates/         # .epp / .erb rendered into files
├── files/             # static file sources
├── functions/         # custom Puppet functions
├── lib/facter/        # custom facts
└── data/ + hiera.yaml # module-level data
class profile::web { ... }
Classes group resources; namespacing follows the file path.
define apache::vhost(...) { }
Defined types are reusable resource templates instantiated many times.
include profile::web
Declare a class once; idempotent. contain binds it for ordering.

The Compilation Pipeline

StageWhat happensInput
ClassificationDecide which classes apply to the node (ENC, site.pp, Hiera).node name, environment
Fact injectionNode's facts become top-scope variables ($facts).Facter payload
EvaluationManifests run as code: conditionals, loops, lookups, functions.manifests + data
Resource collectionDeclared resources accumulate; duplicates rejected.evaluated classes
Graph outputRelationships resolved into a DAG — that DAG is me.metaparameters
Warning Evaluation is compile-time on the master; resource application is run-time on the agent. A duplicate resource declaration fails compilation before any change is attempted.

Facter & the Trust Boundary

Ordinary facts

Facter gathers system data (OS, network, hardware) on the agent and ships it to the master. These are self-reported and could be spoofed by a compromised node.

Trusted facts

$trusted is derived from the agent's signed certificate, not from Facter, so it cannot be forged. Use it for security-sensitive classification.

$facts['os']['family']
Structured fact access; basis for provider and template choices.
$trusted['certname']
Cert-derived identity — the trust boundary for authorization.

Relationships, Ordering & Containment

require => Package['nginx']
This resource applies after the named one — an edge in my graph.
before => Service['nginx']
Inverse of require; the same edge stated from the other side.
notify => Service['nginx']
Ordering plus a refresh signal when this resource changes.
subscribe => File['nginx.conf']
Inverse of notify; service restarts when the watched file changes.
File['a'] -> Service['b']
Chaining arrow; ~> adds refresh, like notify.
contain ::module::service
Pulls a class inside another's containment so ordering edges apply transitively.
Note My graph must be acyclic; a dependency cycle is a compile failure. Within these constraints the agent is free to apply unordered resources in any order.

Hiera & Data Separation

lookup('profile::web::port')
Resolve a value through the layered hierarchy at compile time.
hierarchy: [ "nodes/%{trusted.certname}", "common" ]
Ordered layers; the first match wins for normal lookups.
lookup_options: merge: deep
Merge across layers instead of first-found for hashes/arrays.
class web (String $port = '80') { }
Automatic parameter lookup binds Hiera keys to class params by name.