Quick ref
puppet agent -t trigger a run: request catalog, apply, report--noop simulate convergence, change nothingpuppet catalog compile NODE compile my graph for a nodefacter -p show facts the master will trustpuppet resource service nginx introspect live state as a resourcepuppet lookup KEY --node N resolve Hiera data for a nodeWhat 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 serviceList 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::webDeclare a class once; idempotent.
contain binds it for ordering.The Compilation Pipeline
| Stage | What happens | Input |
|---|---|---|
Classification | Decide which classes apply to the node (ENC, site.pp, Hiera). | node name, environment |
Fact injection | Node's facts become top-scope variables ($facts). | Facter payload |
Evaluation | Manifests run as code: conditionals, loops, lookups, functions. | manifests + data |
Resource collection | Declared resources accumulate; duplicates rejected. | evaluated classes |
Graph output | Relationships 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::servicePulls 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: deepMerge 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.