Skip to content
Puppet Guide — Infrastructure Automation and Configuration Management

Puppet Guide — Infrastructure Automation and Configuration Management

DodaTech Updated Jun 7, 2026 10 min read

Puppet is a mature configuration management tool that automates infrastructure provisioning, enforces desired system state through declarative code, and manages thousands of servers from a central control point using its own domain-specific language.

What You’ll Learn

You’ll understand Puppet’s master/agent architecture, write manifests and classes in the Puppet DSL (Domain Specific Language), structure code with modules, gather system facts with Facter, separate data from code with Hiera, and extend Puppet with custom types and providers.

Why Puppet Matters

Configuration management is essential when you manage more than a handful of servers. Manual SSH-based administration doesn’t scale, is error-prone, and creates configuration drift — where servers gradually diverge from their intended state. Puppet ensures every server in your fleet stays in its declared state, automatically correcting drift. DodaTech’s infrastructure team uses Puppet to manage 200+ production servers, ensuring consistent security baselines, package versions, and firewall rules across the entire fleet.

Puppet Architecture

    flowchart LR
  A[Puppet Master] --> B[Catalog Compilation]
  B --> C[Agent Nodes]
  
  D[Manifests] --> A
  E[Modules] --> A
  F[Hiera Data] --> A
  G[Facter Facts] --> C
  C --> G
  
  H[Agent Reports] --> A
  A:::current
  
  classDef current fill="#FFA100",color:#fff,stroke:#333,stroke-width:2px
  
Prerequisites: Familiarity with Linux system administration (packages, services, files). Basic Bash scripting knowledge is helpful.

How Puppet Works

Puppet operates on a desired state model — you declare what the system should look like, and Puppet makes it so:

  1. Agent runs on each node (typically every 30 minutes via cron or systemd timer)
  2. Agent collects facts about the system (OS, IP, memory, etc.) using Facter
  3. Agent sends facts to the Puppet Master (or applies locally in Puppet Apply mode)
  4. Master compiles a catalog — a list of resources and their desired states
  5. Agent applies the catalog, reporting any changes or errors
  6. Agent sends a report back to the master

Installing Puppet

# On the Puppet Master server
wget https://apt.puppet.com/puppet7-release-focal.deb
sudo dpkg -i puppet7-release-focal.deb
sudo apt update
sudo apt install puppetserver

# Start the server
sudo systemctl start puppetserver
sudo systemctl enable puppetserver

# On each agent node
sudo apt install puppet-agent
# Configure /etc/puppetlabs/puppet/puppet.conf
# [main]
# server = puppet-master.dodatech.com
# certname = webserver-01.dodatech.com

# Start agent
sudo systemctl start puppet
sudo puppet agent --test  # First run (requires certificate signing)

Writing Manifests — The Puppet DSL

Manifests are .pp files containing Puppet code. They declare resources — the fundamental units of configuration:

# site.pp — Main manifest
# Ensure Nginx is installed and running

package { 'nginx':
  ensure => installed,
}

service { 'nginx':
  ensure  => running,
  enable  => true,
  require => Package['nginx'],
}

file { '/etc/nginx/sites-available/dodatech.conf':
  ensure  => file,
  content => template('dodatech/nginx.conf.erb'),
  owner   => 'root',
  group   => 'root',
  mode    => '0644',
  require => Package['nginx'],
  notify  => Service['nginx'],
}

file { '/etc/nginx/sites-enabled/dodatech.conf':
  ensure  => link,
  target  => '/etc/nginx/sites-available/dodatech.conf',
  require => File['/etc/nginx/sites-available/dodatech.conf'],
  notify  => Service['nginx'],
}

Output: Puppet ensures Nginx is installed (installs it if missing), ensures the service is running and enabled at boot, writes the configuration file from a template, creates the sites-enabled symlink, and restarts Nginx if the config changes.

Resource Types

Puppet has built-in resource types for common system objects:

# Common resource types

# Package management
package { 'curl':
  ensure => installed,
}

package { 'apache2':
  ensure => '2.4.41-4ubuntu3.13',  # Specific version
}

# Service management
service { 'sshd':
  ensure    => running,
  enable    => true,
  hasstatus => true,
  hasrestart => true,
}

# File management
file { '/etc/motd':
  ensure  => file,
  content => "Welcome to DodaTech Production Server\n",
  owner   => 'root',
  group   => 'root',
  mode    => '0644',
}

# User management
user { 'deploy':
  ensure     => present,
  uid        => '2001',
  gid        => '2001',
  shell      => '/bin/bash',
  home       => '/home/deploy',
  managehome => true,
}

# Cron jobs
cron { 'daily-backup':
  command => '/usr/local/bin/backup.sh',
  user    => 'root',
  hour    => 2,
  minute  => 0,
}

# Exec (run arbitrary commands — use sparingly)
exec { 'update-ssl-cert':
  command     => '/usr/bin/certbot renew',
  refreshonly => true,
  subscribe   => File['/etc/nginx/ssl/dodatech.pem'],
}

Classes and Modules

Classes group related resources into reusable units:

# modules/nginx/manifests/init.pp
class nginx (
  String $ensure        = 'installed',
  String $service_ensure = 'running',
  Boolean $enable_service = true,
  String $config_source  = '',
) {
  
  package { 'nginx':
    ensure => $ensure,
  }
  
  service { 'nginx':
    ensure  => $service_ensure,
    enable  => $enable_service,
    require => Package['nginx'],
  }
  
  # Main configuration
  if $config_source != '' {
    file { '/etc/nginx/nginx.conf':
      ensure  => file,
      source  => $config_source,
      owner   => 'root',
      group   => 'root',
      mode    => '0644',
      require => Package['nginx'],
      notify  => Service['nginx'],
    }
  }
}
# modules/nginx/manifests/vhost.pp — Defined type
define nginx::vhost (
  String $server_name,
  String $root,
  Boolean $ssl     = false,
  String $ssl_cert = '',
  String $ssl_key  = '',
) {
  
  file { "/etc/nginx/sites-available/${name}.conf":
    ensure  => file,
    content => epp('nginx/vhost.conf.epp', {
      'server_name' => $server_name,
      'root'        => $root,
      'ssl'         => $ssl,
      'ssl_cert'    => $ssl_cert,
      'ssl_key'     => $ssl_key,
    }),
    owner   => 'root',
    group   => 'root',
    mode    => '0644',
    notify  => Service['nginx'],
  }
  
  file { "/etc/nginx/sites-enabled/${name}.conf":
    ensure  => link,
    target  => "/etc/nginx/sites-available/${name}.conf",
    require => File["/etc/nginx/sites-available/${name}.conf"],
    notify  => Service['nginx'],
  }
}

# Usage:
# nginx::vhost { 'dodatech':
#   server_name => 'dodatech.com',
#   root        => '/var/www/dodatech',
#   ssl         => true,
# }

Facter — System Facts

Facter gathers system information and exposes it as variables in Puppet:

# Run Facter directly
facter os
# {
#   "family" => "Debian",
#   "name" => "Ubuntu",
#   "release" => { "full" => "22.04", "major" => "22.04" }
# }

facter networking
# {
#   "ip" => "10.0.1.42",
#   "hostname" => "webserver-01",
#   "domain" => "dodatech.com"
# }

facter memory
# {
#   "system" => { "total" => "16.00 GiB", "used" => "8.23 GiB" }
# }

Use facts in your manifests:

# Conditional logic based on facts
if $facts['os']['family'] == 'RedHat' {
  package { 'httpd': ensure => installed }
} elsif $facts['os']['family'] == 'Debian' {
  package { 'apache2': ensure => installed }
}

# Use facts for configuration
file { '/etc/rsyslog.d/remote.conf':
  content => "*.* @${facts['networking']['ip']}:514\n",
}

# Custom fact (from a module's facts.d directory)
# modules/dodatech/lib/facter/app_version.rb
# Facter.add('app_version') do
#   setcode do
#     Facter::Core::Execution.execute('cat /opt/dodatech/VERSION')
#   end
# end

Output: Puppet automatically adapts to the target OS — installing httpd on CentOS and apache2 on Ubuntu. Facts like IP and hostname are available for configuration templates. Custom facts can expose application-specific data.

Hiera — Separating Data from Code

Hiera manages configuration data in YAML files, keeping logic and data separate:

# hieradata/common.yaml
ntp_servers:
  - 0.pool.ntp.org
  - 1.pool.ntp.org
  - 2.pool.ntp.org

firewall_rules:
  - { port: 22,  protocol: tcp, action: accept, source: '10.0.0.0/8' }
  - { port: 80,  protocol: tcp, action: accept, source: '0.0.0.0/0' }
  - { port: 443, protocol: tcp, action: accept, source: '0.0.0.0/0' }

syslog_server: 'logs.dodatech.com'
# hieradata/nodes/webserver-01.dodatech.com.yaml
# Node-specific overrides
nginx::vhosts:
  dodatech:
    server_name: 'dodatech.com'
    root: '/var/www/dodatech'
    ssl: true
  api:
    server_name: 'api.dodatech.com'
    root: '/var/www/api'
    ssl: true

firewall_rules:
  - { port: 3000, protocol: tcp, action: accept, source: '10.0.0.0/8' }
# Puppet code uses Hiera data automatically
class profile::base {
  $ntp_servers = lookup('ntp_servers')
  
  package { 'chrony': ensure => installed }
  
  file { '/etc/chrony/chrony.conf':
    content => template('profile/chrony.conf.erb'),
    # Template uses $ntp_servers array
  }
}

Output: Each node gets merged data from hierarchy levels (common → OS → environment → node-specific). The web server gets its specific virtual host configs plus the common firewall rules. No data hard-coded in manifests.

Common Mistakes Beginners Make

  1. Imperative thinking in a declarative system: Puppet describes desired state, not steps to reach it. Don’t write “first install, then configure” — declare both and use require/before for ordering.

  2. Not using idempotent resources: Every resource should be safe to run repeatedly. exec resources are particularly dangerous — use onlyif, unless, or creates to ensure idempotency.

  3. Certificate signing confusion: Puppet’s SSL certificate infrastructure requires explicit signing of agent certificates. Automation with autosign requires careful security configuration.

  4. Deep class nesting: Classes should be shallow and focused. Deep inheritance hierarchies make code hard to understand and debug. Use composition (include multiple classes) instead.

  5. Hard-coding node data in manifests: Use Hiera for environment-specific data. Hard-coding IPs, ports, and paths in .pp files makes the codebase brittle and non-portable.

  6. Testing only on one OS: Puppet manifests behave differently on different OS families. Always test on all target platforms using tools like Beaker or Puppet Development Kit.

Practice Questions

  1. What is the difference between Puppet’s declarative model and imperative scripting?
  2. How does Puppet ensure idempotency in package and service resources?
  3. What is the purpose of Facter in the Puppet ecosystem?
  4. Why should data be separated from code using Hiera?
  5. How do you apply ordering between resources without writing procedural steps?

Answers:

  1. Declarative means you declare the desired end state (“package should be installed”), and Puppet figures out how to reach it. Imperative scripting specifies every step, which is fragile and non-idempotent.
  2. Package resources check if the package is already installed before acting. Service resources check the current state before starting/stopping. Both are naturally idempotent.
  3. Facter collects system metadata (OS, IP, memory, etc.) and exposes it as variables in Puppet manifests, enabling conditional logic and data-driven configuration.
  4. Separating data from code allows different teams to manage configuration values without changing Puppet logic, enables environment-specific overrides, and makes code reusable across different deployments.
  5. Use metaparameters like require, before, notify, and subscribe to declare relationships. Puppet’s dependency graph ensures correct ordering.

Challenge

Build a complete LAMP stack module: create classes for Apache, MySQL, and PHP with Hiera-driven configuration, add defined types for virtual hosts and databases, implement custom facts for service health checks, and write Beaker tests that verify idempotency across Ubuntu and CentOS.

Real-World Task

Create a hardened server baseline profile with Puppet: enforce SSH key-only authentication, configure auditd rules, set umask defaults, install and configure fail2ban, deploy custom sudoers policies, enforce NTP synchronization, and configure centralized logging — all driven by Hiera data per environment.

FAQ

What is the difference between Puppet and Ansible?
: Puppet uses a master/agent architecture with a declarative DSL and runs as a service on nodes. Ansible is agentless, uses SSH for push-based execution, and uses YAML playbooks. Puppet is better for large-scale, continuously enforced state; Ansible is simpler for ad-hoc tasks.
Do I need a Puppet Master server?
: Not necessarily. Puppet Apply runs manifests locally without a master, suitable for testing and smaller deployments. The master/agent model provides centralized reporting, certificate management, and scalability.
How does Puppet handle certificate management?
: Puppet uses its own internal CA. Agents generate CSR on first run, the master signs them (manually or via autosign), and all subsequent communication uses TLS-encrypted connections.
Can Puppet manage Windows servers?
: Yes. Puppet supports Windows with resource types for registry, services, packages, files, and scheduled tasks. The same DSL works across Linux and Windows.
How do I test Puppet manifests?
: Use puppet parser validate for syntax checking, puppet apply --noop for dry-runs, and the Puppet Development Kit (PDK) with Beaker for full integration testing.

Try It Yourself

# Install Puppet Agent on a test VM
sudo apt update && sudo apt install -y puppet-agent

# Write a simple manifest
cat > test.pp << 'EOF'
package { 'htop':
  ensure => installed,
}

file { '/tmp/puppet-test.txt':
  content => "Managed by Puppet\n",
  mode    => '0644',
}
EOF

# Apply it
sudo puppet apply test.pp

# Check the result
cat /tmp/puppet-test.txt
dpkg -l | grep htop

What’s Next

Related topics: Bash, Linux, Chef, Ansible

What’s Next

Congratulations on completing this Puppet tutorial! Here’s where to go from here:

  • Practice daily — Consistency is more important than long study sessions
  • Build a project — Apply what you learned by building something real
  • Explore related topics — Check out other tutorials in the same category
  • Join the community — Discuss with other learners and share your progress

Remember: every expert was once a beginner. Keep coding!

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro