Ansible, Puppet, Chef and SaltStack: Why Ansible Is Still My Default in 2026
There are four names that come up when you ask how to manage configuration at scale. I’ve used all four, in production, over the last fifteen years. The answer to “which one” is not the one that wins benchmarks — it’s the one that matches how your team thinks about change.
The shortlist hasn’t changed since the early 2010s: Ansible, Puppet, Chef, SaltStack. The ownership has changed. The licences have changed. The community gravity has very much changed. Ansible sits at 31.94% market share for new projects as of early 2026 and is, by a wide margin, the most-adopted tool for new configuration-management work. Puppet holds 12.41%, Chef around 6.70%, and Salt has fallen out of the top-tier mindshare entirely even though the codebase is still actively developed under Broadcom.
I want to walk through each one honestly, then explain why — after fifteen years of using all four — Ansible is still the one I reach for, and where it’s genuinely the wrong answer.
Ansible
Two things about Ansible in 2026 matter more than the version number:
- Red Hat Ansible Automation Platform 2.6 is the current recommended release, with 2.5 still receiving patch updates (a patch shipped on May 4, 2026).
- AAP 2.6 is the last release with RPM-based installers. Everything after it is containerised. If you’ve been postponing the move to containers, the deadline is now.
The community side keeps its own cadence. The ansible community
package ships two majors a year and the ansible-core engine
gets a minor every four weeks or so. AAP 2.4 is out of
maintenance on June 30, 2026.
The architecture is the part everyone knows: agentless, SSH for Linux and WinRM/SSH for Windows, YAML playbooks, push-based execution from a control node. No daemons sitting on every host waiting for a heartbeat. If the control node is down, nothing runs. If a target is down, that target is skipped and the rest of the run continues.
A minimal playbook that installs and starts nginx on a group of hosts looks like this:
- name: Web servers
hosts: web
become: true
tasks:
- name: Install nginx
ansible.builtin.package:
name: nginx
state: present
- name: Enable and start nginx
ansible.builtin.service:
name: nginx
enabled: true
state: started
- name: Deploy site config
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
mode: "0644"
notify: Reload nginx
handlers:
- name: Reload nginx
ansible.builtin.service:
name: nginx
state: reloaded
There is no controller to install, no certificate dance, no agent package to push out before you can do anything. You can run it from your laptop the first time, then promote the same playbook to a shared control node, then promote that into a CI job, with zero changes to the playbook itself.
Where Ansible shines:
- Agentless. SSH is already there, your fleet already has it, there is one fewer thing to break at 2 a.m.
- YAML is hated by everyone and still better than the alternatives. A Ruby DSL or a custom DSL is harder to onboard juniors onto.
- Idempotency is straightforward. Run a playbook twice, same result. Modules are written around state, not script lines.
- Galaxy and Collections. The ecosystem of community roles and vendor-published collections (Cisco, Arista, AWS, Azure, Red Hat itself) is enormous.
- Ad-hoc execution is a real feature, not an escape hatch.
ansible web -m shell -a 'uptime'is sometimes exactly what you want, and Ansible treats it as a first-class operation.
Where it doesn’t shine:
- Scale. Ansible’s push model with SSH starts to feel its limits somewhere past a few thousand managed hosts in one run. Mitigations exist — strategies, mitogen in the past, AAP execution environments — but a pull-based system with a permanent agent will always scale more linearly.
- Real-time reactive config. If you need “host X reports event Y, apply state Z within milliseconds”, Ansible is not the tool. That is Salt’s home turf.
- Drift detection. You can run a playbook in check mode and look at the diff, but there is no built-in continuous reconciliation loop the way Puppet’s agent provides one.
Puppet
Puppet is the oldest of the four. Perforce acquired Puppet in April 2022 for around $150M, and the project’s centre of gravity has moved noticeably since.
The current numbered releases are Puppet Enterprise 2025.10.0 and Puppet Enterprise 2023.8.9 on the long-term track. From the next major (projected August 2026) Puppet Enterprise leaves the “LTS / STS” model and moves to a “Latest / Latest -1” cadence — shorter support windows, faster releases.
The licence situation is more nuanced than it gets credit for. The core puppet-agent code stays Apache 2.0. But starting in early 2025, Perforce began shipping hardened binaries from a private location and tightening commit cadence on the public repos. Licensing restrictions now apply to Puppet Development Kit (PDK) 3.5+ and to Puppet Security Compliance Management / Enforcement. There has been enough open-community concern about this that a community fork — informally being called “Muppet” — is in active discussion.
Architecturally Puppet is the opposite shape of Ansible.
Agent-based, pull model, declarative DSL. A puppet-agent runs
on every managed node, talks to a puppetserver on a fixed
interval (default 30 minutes), pulls its catalog, and reconciles
the host to the declared state. No central push needed.
A minimal Puppet manifest for the same nginx scenario:
class profile::web {
package { 'nginx':
ensure => installed,
}
file { '/etc/nginx/nginx.conf':
ensure => file,
mode => '0644',
content => template('profile/nginx.conf.erb'),
require => Package['nginx'],
notify => Service['nginx'],
}
service { 'nginx':
ensure => running,
enable => true,
}
}
It is genuinely elegant if you like declarative thinking. The catalog compiles, the agent applies the diff, the next run picks up any drift and corrects it. Puppet has continuous reconciliation built in.
Where Puppet shines:
- Continuous reconciliation. The agent runs whether you’re paying attention or not. Drift gets corrected silently.
- Strong declarative model. “This is what the host should look like.” The order of declarations doesn’t matter; relationships do.
- Scale. Tens of thousands of nodes, one Puppet server (or a cluster). Pull-based scaling is straightforward.
- Reporting and compliance. PuppetDB, the console, and the compliance modules give you proper at-rest reports — who looks like what, not just what did we run last.
Where it doesn’t shine:
- Onboarding. The Puppet DSL is a real DSL with its own rules and quirks. Hiera, classification, environments, code-manager — there is a lot of conceptual weight before someone can ship a change confidently.
- Agent footprint. Every node runs a Ruby-based agent. On small edge devices or container hosts, that’s not free.
- Open-source posture. The Perforce-era shift to private hardened binaries and the PDK licence changes have eroded community confidence. The fork talk exists for a reason.
Chef
Chef is owned by Progress Software and consists of Chef Infra, Chef InSpec, Chef Habitat, and Chef Automate. The source code remains Apache 2.0, but since April 2019 Progress has shipped the binaries under a proprietary EULA. The community responded with CINC — “Cinc Is Not Chef” — a distribution of Apache-licensed binaries built from the same source, deliberately functionally identical with only the trademarks removed.
If you are running Chef in 2026 and don’t have a Progress contract, you are very likely running CINC, whether you call it that or not.
The shape: agent-based pull model, like Puppet, but with a
Ruby DSL for the resource definitions. A chef-client (or
cinc-client) runs on a schedule, talks to a Chef Server, downloads
its cookbooks, and converges the host.
A minimal Chef recipe for the same nginx scenario:
package 'nginx' do
action :install
end
template '/etc/nginx/nginx.conf' do
source 'nginx.conf.erb'
mode '0644'
notifies :reload, 'service[nginx]', :delayed
end
service 'nginx' do
action [:enable, :start]
end
The Ruby DSL is the part everyone has an opinion on. For Ruby engineers it’s fluent and natural — you can drop into raw Ruby inside a recipe and do almost anything. For everyone else it’s a learning curve plus a constant low-level worry that too much real code in your cookbooks will turn maintenance into a Ruby project.
Where Chef shines:
- Power. If your team is Ruby-fluent, the expressiveness of the DSL has no real ceiling. Custom resources are real code.
- InSpec. Compliance-as-code in InSpec is genuinely strong. Even shops that have moved off Chef Infra often keep InSpec.
- Test Kitchen. A serious local-test-VM story for cookbooks before they hit production.
Where it doesn’t shine:
- Ruby tax. Hiring and onboarding around Ruby in 2026 is harder than it was in 2016. New engineers are far more likely to know Python or YAML.
- Operational weight. Chef Server, workstations, knife, Berks, Policyfiles — the surface area is real, and a lot of the docs still assume you know it all.
- Adoption trajectory. New projects are not picking Chef in 2026 at meaningful rates. The market-share number tells the story: about 6.70%, and most of that is existing fleets.
SaltStack
Salt — the project formally lives under the Salt Project umbrella now — has had the bumpiest ownership path of the four. SaltStack → VMware (2020) → Broadcom (2023). The product formerly known as VMware Tanzu Salt is now VMware Cloud Foundation SaltStack, supported by Broadcom through October 2028. The open-source Salt engine is at 3007.14 stable, with 3008.0rc4 in release-candidate testing as of May 2026.
Salt’s defining characteristic is the event bus. Architecturally it’s pub/sub: every minion talks to the master over ZeroMQ; events flow constantly; reactors subscribe to events and trigger states. This makes Salt the only one of the four that’s genuinely event-driven out of the box — “host X joined, configure it now” or “service Y degraded, run remediation Z” are patterns Salt was designed for.
The default is agent-based: salt-minion on every node, talking to
a salt-master. There’s also salt-ssh for an agentless,
push-style mode, but agentless is the secondary use case, not the
primary one.
A minimal Salt state file for nginx:
nginx:
pkg.installed: []
service.running:
- enable: True
- require:
- pkg: nginx
- watch:
- file: /etc/nginx/nginx.conf
/etc/nginx/nginx.conf:
file.managed:
- source: salt://nginx/nginx.conf.jinja
- template: jinja
- mode: 644
The YAML + Jinja + states model is recognisable if you’ve used Ansible — but the runtime semantics underneath are a different beast.
Where Salt shines:
- Event-driven config. The reactor / beacon / event-bus model is genuinely unique. Real-time response to host events is a first-class feature, not a bolt-on.
- Speed at scale. ZeroMQ pub/sub plus persistent minions means Salt can fan out commands across very large fleets faster than push-over-SSH ever will.
- Pillar. A clean separation between state (what to apply) and pillar (sensitive, per-host data), with good targeting.
Where it doesn’t shine:
- Community gravity has shrunk. Post-Broadcom, the energy around Salt is noticeably lower than it was five years ago. New projects rarely pick Salt as their first choice in 2026.
- Agent on every node. The cost is small, but it’s there, and it has been the source of more than one production headache for me over the years.
- Mixed-mode complexity. Salt is at its best when everything is fully Salt-managed. Hybrid deployments — half Salt, half something else — tend to surface its rougher edges.
A side-by-side
| Ansible (AAP 2.6) | Puppet (PE 2025.10) | Chef Infra / CINC | Salt (3007.14) | |
|---|---|---|---|---|
| Architecture | Agentless | Agent-based | Agent-based | Agent-based (or salt-ssh) |
| Direction | Push | Pull | Pull | Push + event bus |
| DSL | YAML + Jinja | Puppet DSL | Ruby DSL | YAML + Jinja |
| Style | Imperative-ish | Declarative | Imperative (Ruby) | Declarative |
| Continuous drift fix | Manual / scheduled run | Built-in (~30 min) | Built-in (~30 min) | Built-in via beacons |
| Owner | Red Hat / IBM | Perforce | Progress Software | Broadcom |
| Open-source posture | Strong | Tightening | Source open, binaries proprietary (CINC fills the gap) | Strong, low activity |
| Learning curve | Low | Medium-high | High (Ruby) | Medium |
| Sweet spot | Heterogeneous fleets, ad-hoc tasks | Large, declarative, drift-controlled fleets | Ruby shops, complex custom resources | Event-driven, large fleets |
That table is the cheat-sheet version. The longer version of the table fits in two words: shape matters. None of these tools is bad. They’re optimised for different shapes of operations team.
Why Ansible is still my default
After fifteen years of running all four, the reasons I keep reaching for Ansible are not exciting:
- Agentless means one less thing to break at 2 a.m. Every on-call shift I’ve ever done has had at least one incident involving an agent that died, a certificate that expired, or a master that lost quorum. Ansible has none of that. SSH is there or it isn’t, and if it isn’t, the problem is the host, not the tool.
- YAML is hated by everyone and still better than the alternatives. I have onboarded juniors onto Puppet DSL, Chef Ruby, Salt YAML+Jinja, and Ansible YAML+Jinja. The Ansible onboarding is shorter. Every time.
- Idempotency is straightforward. Run a playbook twice, same result. Modules are written around state, not commands. The mental model fits in your head before lunch.
- The ecosystem is enormous. Galaxy, collections, vendor- published modules for networking gear, hypervisors, clouds — the long tail is longer than Puppet’s or Chef’s, and much longer than Salt’s.
- It fits the “run a playbook and go” mindset better than a permanent agent. Most of what I run is not a fleet of 50,000 identical web servers. It’s a handful of hosts each, dozens of different shapes, semi-permanent control nodes I can SSH to. For that shape, push is the right answer.
Where Ansible is the wrong answer
I want to be specific about this because the loudest Ansible advocates often aren’t.
- Pull-based, drift-corrected fleets of thousands of identical nodes. Puppet’s continuous-reconciliation model wins this. I would not push Ansible at 30,000 identical hosts where drift correction must happen on a 30-minute cadence without operator intervention.
- Real-time reactive configuration. Salt’s event bus is the right tool for “host X reports condition Y, apply Z within seconds.” Ansible can be wired to do this through AAP automation controller and webhooks, but it’s an additional layer; Salt has it natively.
- A team that already lives in Ruby. If your platform team writes Ruby every day and has years of Chef cookbooks that work, there is no benefit to a migration. Chef is the right tool for that team.
Closing
The best configuration management tool is the one your team will actually use. For me, after Puppet manifests that rotted, Chef cookbooks that nobody wanted to maintain, and Salt states that required a minion on every VM I touched — Ansible is the one I reach for. Not because it’s perfect. Because it’s the least wrong for the shape of infrastructure I run.
If you’re on Puppet, Chef, or Salt and it’s working, none of this is a reason to migrate. The cost of changing configuration management is always higher than it looks on the spreadsheet, and the benefit has to be very real to pay it. The decision point is new fleets, new greenfield, new teams without an existing cookbook investment — and for that decision, in 2026, Ansible is where I’d point them.
If you’re using Puppet, Chef, or Salt in a way that genuinely beats Ansible for your workload — I’d like to hear it. That’s the information I’m most likely to be missing.