The Puppet Labs Issue Tracker has Moved:

This issue tracker is now in read-only archive mode and automatic ticket export has been disabled. Redmine users will need to create a new JIRA account to file tickets using See the following page for information on filing tickets with JIRA:

« Previous - Version 4/20 (diff) - Next » - Current version
James Turnbull, 03/14/2010 05:55 am

Big picture

Puppet is a declarative language for expressing system configuration, a client and server for distributing it, and a library for realizing the configuration.

Rather than approaching server management by automating current techniques, Puppet reframes the problem by providing a language to express the relationships between servers, the services they provide, and the primitive objects that compose those services. Rather than handling the detail of how to achieve a certain configuration or provide a given service, Puppet users can simply express their desired configuration using the abstractions they’re used to handling, like service and node, and Puppet is responsible for either achieving the configuration or providing the user enough information to fix any encountered problems.

This document is a supplement to the [Puppet Introduction(] and it is assumed that readers are familiar with the contents of that document.

Less Detail, More Information

A correct configuration must obviously provide the appropriate details, but a configuration tool should understand that the details are generally the easy part. The hard part of system configuration is understanding the complex relationships between services and the objects that comprise those services. One of the primary goals of Puppet is to allow users to push the details into lower levels of the configuration and pull the relationships into the foreground.

For instance, take a typical Apache web server deployment. Puppet allows one to encapsulate all of the primitives necessary for successful deployment into one reusable object, and that object can even be abstracted to support multiple apache versions. Here’s how a simple apache definition might look for a Debian server (Debian uses apache for 1.x versions and apache2 for 2.x versions):

define apache(version, conf, user, group) {
    # abstract across apache1 and apache2
    $name = $version ? {
        1 => "apache",
        2 => "apache2",
    package{ $name:
        install => true,

    file { "$conf":
        user   => "$user",
        group  => "$group",
        source => "$conf",

    # we want the service to restart if the config file changes
    # or if the package gets upgraded
    service { "$name":
        running  => true,
        subscribe => [file["$conf"], package["$name"]],

Now, with this configuration, one can easily set multiple servers up to run different versions of apache. The key benefit here is that the information necessary to run apache correctly is separated from the decision to do so on a given host. For example:

# import our apache definition file
import "apache"

node server1 {
    # use a locally-available config file
    apache {
        version => 1,
        conf  => "/nfs/configs/apache/server1.conf",
        user  => "www-data",
        group => "www-data",

node server2 {
    # use a config that we pull from elsewhere
    apache {
        version => 2,
        conf    => "http://configserver/configs/server2/httpd.conf"
        user    => "www-data",
        group   => "www-data",

Notice that our node configuration only specifies 1) that a given server is running Apache, and 2) the information necessary to differentiate this instance of apache from another instance. If a given detail is going to be the same for all apache instances (such as the fact that the service should be running and that it should be restarted if the configuration file changes), then that detail does not have to be specified every time an Apache instance is configured.

Describing Configuration

Puppet’s declarative language separates the “what” of the configuration from the “how”. The “how” is pushed into the library, which generally provides the ability to manage a given entity on multiple platforms, isolating the Puppet user from platform details.

Language Example: Class Hierarchy Using Inherit

Inheritance can be used to override default values defined in base classes:

class base {

    file { "/etc/sudoers":
        owner => "root",
        group => "root",
        mode  => 440

# FreeBSD has a "wheel" group instead of a "root" group
class freebsd inherits base {
    file [ "/etc/sudoers" ] {
        group => "wheel",


Distributing Configuration

The Puppet framework Library consists of a client and server.

(picture/diagram: client, server, site-config –> host-config)

A Puppet server is aware of the full configuration. As some component’s configuration aspects depend on the configuration of other components (e.g. the firewall config includes the ports used by webservers), generating configuration for a component requires being aware of full configuration.

A Puppet client that runs on a specific host (or perhaps the same host as its Puppet Server) is generally only concerned with the components to be configured on that host.

Puppet Clients normally request or “pull” configuration from their server. The Server processes the configuration request for the host using a pre-generated tree model of the classes and definitions from the site-config.

When configuration needs to be “pushed” to the clients, the Server can be asked to attempt to trigger each client to request “pull” a new host configuration.

Example: Puppet server, library

You can read more about the Puppet language in the [[Language Tutorial]] .

Realizing the Configuration

The Puppet Client Library contains the component knowledge of how to reach desired states and configurations for several objects: File, Package, etc.

Example: Puppet Client, library:

…todo: file: transfer, mode, ownership…

You can read more about the Puppet language in the [[Language Tutorial]] .

Some components such as the webserver and firewall, from the simple-website example, require additional code to reach “closure”. “Closure” is when the component is entirely responsible for implementing its own configuration.

Much as database applications abstract the mechanics of storing, indexing, and searching their data, a component ideally should abstract the specifics of how to store, confirm, and implement its requested configuration.