Puppet Guide — Infrastructure Automation and Configuration Management
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
How Puppet Works
Puppet operates on a desired state model — you declare what the system should look like, and Puppet makes it so:
- Agent runs on each node (typically every 30 minutes via cron or systemd timer)
- Agent collects facts about the system (OS, IP, memory, etc.) using Facter
- Agent sends facts to the Puppet Master (or applies locally in Puppet Apply mode)
- Master compiles a catalog — a list of resources and their desired states
- Agent applies the catalog, reporting any changes or errors
- 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
# endOutput: 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
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/beforefor ordering.Not using idempotent resources: Every resource should be safe to run repeatedly.
execresources are particularly dangerous — useonlyif,unless, orcreatesto ensure idempotency.Certificate signing confusion: Puppet’s SSL certificate infrastructure requires explicit signing of agent certificates. Automation with autosign requires careful security configuration.
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.
Hard-coding node data in manifests: Use Hiera for environment-specific data. Hard-coding IPs, ports, and paths in
.ppfiles makes the codebase brittle and non-portable.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
- What is the difference between Puppet’s declarative model and imperative scripting?
- How does Puppet ensure idempotency in package and service resources?
- What is the purpose of Facter in the Puppet ecosystem?
- Why should data be separated from code using Hiera?
- How do you apply ordering between resources without writing procedural steps?
Answers:
- 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.
- Package resources check if the package is already installed before acting. Service resources check the current state before starting/stopping. Both are naturally idempotent.
- Facter collects system metadata (OS, IP, memory, etc.) and exposes it as variables in Puppet manifests, enabling conditional logic and data-driven configuration.
- 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.
- Use metaparameters like
require,before,notify, andsubscribeto 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
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 htopWhat’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