Request an exact quote
IaC & Secrets migration path

From Puppet Enterprise to Ansible

Move off per-node Puppet Enterprise licensing to agentless Ansible, translating manifests and Hiera to playbooks and group_vars without losing enforcement.

Effort
Low
Est. timeline
~12 wks
Ansible model
Free OSS / AAP paid
Open source
Yes
▶ Model your savings in the interactive calculator

Since the Perforce acquisition folded Puppet into a commercial bundle, the per-node math on Puppet Enterprise has pushed a lot of teams to re-price their config management. Ansible is the usual landing spot. Before you start translating code, understand the one architectural difference that drives every decision below.

The mental-model change

Puppet is a pull system. Every node runs an agent that wakes up on an interval (default 30 minutes), pulls catalog from the master, and converges local state to match. Enforcement is continuous and automatic. Nobody has to “run” anything.

Ansible is a push system. A control node opens SSH, ships modules to the target, executes them, and disconnects. Nothing runs on the managed host between plays. There is no agent, no certificate authority, no PuppetDB. That removes a whole tier of infrastructure, but it also removes the thing that quietly corrected drift every half hour. Hold that thought, because it dictates your enforcement strategy at the end.

Translating the code

The vocabulary maps cleanly once you accept push vs pull:

  • A Puppet manifest (.pp) becomes a playbook or a task file (.yml).
  • A Puppet module becomes an Ansible role with the same tasks/, templates/, files/, defaults/ layout.
  • A Puppet resource (package, file, service) becomes an Ansible module call (ansible.builtin.package, template, service).
  • Puppet facts become Ansible facts, gathered by the setup module and namespaced under ansible_facts.

A concrete before/after. Puppet:

package { 'nginx': ensure => installed }
service { 'nginx': ensure => running, enable => true }

Ansible:

- name: nginx present and running
  block:
    - ansible.builtin.package:
        name: nginx
        state: present
    - ansible.builtin.service:
        name: nginx
        state: started
        enabled: true

Both are idempotent. The difference: Puppet enforces this ordering through its dependency graph, while Ansible runs top to bottom. You make ordering explicit instead of relying on require and before arrows.

Hiera becomes group_vars

Hiera is where most of the real logic lives, and it ports more naturally than people expect. Your hierarchy of YAML data files maps to Ansible’s group_vars/ and host_vars/ directories, resolved by inventory group and hostname. A common.yaml in Hiera becomes group_vars/all.yml. Per-environment Hiera layers become inventory groups (group_vars/production.yml).

For secrets, eyaml becomes ansible-vault. Encrypt the file or individual values:

ansible-vault encrypt_string 's3cr3t' --name 'db_password'

Migrate node by node, run both

Do not flip the fleet at once. The agent and SSH coexist fine on the same host, so run both systems in parallel during transition:

  1. Pick one role (say, base hardening). Port it to Ansible.
  2. Build an inventory of a few canary nodes.
  3. Run the playbook with --check --diff against those nodes while Puppet still owns them.
  4. When Ansible’s diff is empty, remove that resource from the Puppet manifest so the two do not fight.
  5. Repeat role by role until the agent manages nothing, then uninstall it.

The --check --diff step is your safety net. It tells you exactly what Ansible would change before it changes anything, which Puppet’s noop mode does too, so the workflow feels familiar.

Proving each role, then wiring up CI

Because the two systems run side by side, your validation story during the transition is a diff, not a leap of faith. For each ported role, the canary loop above, run in --check --diff, confirm an empty diff, then remove the resource from Puppet, is the whole safety mechanism. Treat a non-empty diff on a node Puppet still manages as a signal that your translation is incomplete, not as license to apply.

Wiring this into CI changes shape from Puppet’s model. Puppet’s pipeline typically validated manifests and let agents pull the result; Ansible’s pipeline runs the playbooks itself against inventory. Lint the roles, run them in check mode against a staging inventory, and gate promotion on a clean diff there before any production run. Keep the inventory itself under version control alongside the roles, since in an agentless world the inventory is the source of truth for what gets managed, replacing the agent’s implicit membership in the Puppet master’s catalog.

Rollback is worth thinking through explicitly, because Ansible has no automatic reconvergence to lean on. If a play makes a change you need to undo, you undo it with another play or by re-running the previous, known-good role. Keeping roles idempotent is what makes that safe: re-running the prior version returns the host to its intended state. During the migration itself, the ultimate rollback is that Puppet still owns anything you have not yet cut over, so a botched role is contained to its canary set rather than the fleet.

Where Puppet still wins

Be honest with yourself here, because this is where migrations get regretted.

  • Continuous enforcement and drift correction. Puppet’s agent re-converges every 30 minutes with no scheduler. Ansible only enforces when you run it. If a host is your security baseline and someone edits a config by hand, Puppet silently fixes it; Ansible does not notice until the next push.
  • Very large fleets. Pull scales better than push. Ten thousand agents checking in independently beats one control node opening ten thousand SSH sessions. Ansible needs mitogen, forks tuning, or pull mode to compete at that size.
  • PuppetDB reporting and inventory. The queryable history of every catalog run is hard to replicate. Ansible’s reporting is thinner.

To get enforcement back, you need a scheduler. AWX (or commercial AAP) runs playbooks on a cron-like schedule against your inventory, which restores the “every N minutes” convergence. A plain cron job calling ansible-pull on each host gets you closer to Puppet’s actual pull model. Budget for one of these; agentless does not mean enforcement-less for free.

So, is it worth it?

Puppet to Ansible trades an agent, a CA, and per-node licensing for SSH and a control node, and the code translation (manifests to playbooks, modules to roles, Hiera to group_vars) is mechanical once you internalize push vs pull. The real cost is rebuilding continuous enforcement with AWX or ansible-pull, plus the reporting you lose with PuppetDB. Model your node count, your enforcement interval, and the AWX infrastructure in the calculator above before you commit, because at large fleet sizes the licensing savings can get eaten by the operational rebuild.

Tooling & automation for this path

Translate Puppet manifests and modules to Ansible playbooks and roles; convert Hiera data to group/host vars; replace the agent/master model with agentless SSH runs; migrate node by node with idempotence checks.

Primary references: official Ansible documentation ↗ and the Puppet Enterprise documentation ↗ , always verify version-specific behavior against them before you migrate.

Frequently asked questions

How do I translate Hiera data into Ansible without losing the hierarchy?

Hiera's layered YAML maps onto Ansible's group_vars and host_vars, resolved by inventory group and hostname. A common.yaml becomes group_vars/all.yml, and per-environment Hiera layers become inventory groups such as group_vars/production.yml. Encrypted eyaml values move to ansible-vault, either as whole encrypted files or as individual encrypted strings, so the secret data ports across alongside the plaintext.

Puppet corrected drift every run. How do I keep enforcement once I go agentless?

Ansible only enforces when you run it, so you need a scheduler to replace Puppet's automatic convergence. AWX or the commercial AAP can run playbooks on a cron-like schedule against your inventory, and a cron job invoking ansible-pull on each host gets closer to Puppet's actual pull model. Budget for one of these, because agentless does not mean enforcement comes for free.

Do I have to migrate the whole fleet at once?

No, and you should not. The Puppet agent and Ansible over SSH coexist on the same host, so you migrate role by role. Port a role, run it against a few canary nodes with --check --diff while Puppet still owns them, and only remove that resource from the Puppet manifest once Ansible's diff is empty. This avoids the two systems fighting over the same state.

What do I actually give up by leaving Puppet Enterprise?

You lose continuous automatic drift correction, the pull architecture that scales cleanly to very large fleets, and PuppetDB's queryable history of every catalog run. Ansible's reporting is thinner and push scales less gracefully at ten-thousand-node sizes. Model your node count, enforcement interval, and the AWX rebuild in the calculator above, because the licensing savings can be eaten by the operational rebuild at large fleet sizes.

Model your 3-year cost

Pre-filled for Puppet Enterprise → Ansible; adjust every figure with your own numbers. Estimates are illustrative, not vendor quotes, see our methodology.

Sized at 3,000 managed resources, cost is computed on this.
Stay on Puppet Enterprise (3yr)
$225,000
Move to Ansible (3yr + migration)
$84,000
Projected savings
$141,000 (63%)
Payback period
9.1 mo
Build a decision report from these numbers:

Illustrative, editable figures, not vendor pricing (defaults reviewed May 2026).

Request a vendor-accurate Ansible quote

A guided builder that turns your estimates into a requirements report (RFQ) you can send to a vendor, partner, or distributor for a binding quote, then feed the real prices back into the calculator above. How our estimates work.

  1. 1Size it
  2. 2Requirements
  3. 3Your details
  4. 4Channels & export

How big is your Puppet Enterprise estate?

Approximate count of IaC-managed resources / secrets clients. Not sure? Enter rough numbers, the distributor confirms exact counts later.

3,000 managed resources
Default mid-size assumption (3,000 managed resources)