The Problem
Teams reach for configuration management and immediately hit a wall: do you install an agent on every server, run a central master, open extra ports, and keep all of it patched? Agent-based tools like the classic Puppet and Chef setups ask for exactly that. Ansible takes a different path — nothing runs permanently on the servers it manages. Understanding why that works makes everything else about Ansible click into place.
The whole model is one sentence: a control node pushes small programs (modules) to managed nodes over SSH, runs them, collects the results, and cleans up. There is no daemon to install, no port to open beyond SSH, and nothing left running afterward.
Control Node vs Managed Nodes
Ansible has exactly two roles in its topology:
- Control node — the machine where Ansible is installed and where you run
ansible-playbook. It needs Python and Ansible. This can be your laptop, a bastion host, or a CI runner. - Managed nodes — the servers you configure. They need an SSH daemon and a Python interpreter. That is it. They never have Ansible installed.
Because the managed nodes carry no agent, onboarding a new server is just "make sure SSH works." There is no bootstrap problem, no agent version to keep in sync, and no extra attack surface.
The Core Building Blocks
| Concept | What it is |
|---|---|
| <strong>Inventory</strong> | A list of managed hosts, organized into groups (e.g. <code class="inline-code">web</code>, <code class="inline-code">db</code>). |
| <strong>Module</strong> | A small, purpose-built unit of work (<code class="inline-code">apt</code>, <code class="inline-code">copy</code>, <code class="inline-code">service</code>). The actual code pushed to a node. |
| <strong>Task</strong> | A single call to a module with arguments. |
| <strong>Play</strong> | A mapping of a group of hosts to an ordered list of tasks. |
| <strong>Playbook</strong> | A YAML file containing one or more plays. |
| <strong>Facts</strong> | System details (OS, IP, memory) Ansible gathers from each node before running tasks. |
A Minimal Inventory
Inventory can be a simple INI file. Hosts are grouped so a play can target web or db instead of individual machines.
[web]
web01.example.com
web02.example.com
[db]
db01.example.com
[all:vars]
ansible_user=deploy
A Minimal Playbook
A playbook declares the desired state. You describe what should be true, not the step-by-step commands to get there.
---
- name: Configure web servers
hosts: web
become: true
tasks:
- name: Ensure nginx is installed
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
- name: Ensure nginx is running and enabled
ansible.builtin.service:
name: nginx
state: started
enabled: true
Run it with:
ansible-playbook -i inventory.ini site.yml
How a Playbook Run Actually Flows
When you run that command, Ansible does the following for each targeted host, in parallel across hosts:
gather_facts: false).ok, changed, failed, or skipped for every task, ending with a per-host recap.The key detail: modules run on the managed node, but they are transient. Nothing persists between runs. That transience is what makes the agentless model possible.
Idempotency: The Property That Makes It Safe
A well-written Ansible task is idempotent — running it once or a hundred times converges on the same result. The second task above says "nginx should be running." If it already is, Ansible reports ok and changes nothing. If it is not, Ansible starts it and reports changed.
This is why you can safely re-run a playbook any time. Ansible checks current state before acting, so re-runs are cheap and non-destructive. Idempotency comes from the modules, not from magic — which is why ansible.builtin.service is idempotent but a raw command: systemctl start nginx is not.
Agentless vs Agent-Based: The Trade-offs
| Dimension | Agentless (Ansible) | Agent-based (classic pull model) |
|---|---|---|
| Setup on nodes | SSH + Python only | Install + maintain an agent |
| Trigger model | Push, on demand | Pull, on a schedule |
| Extra ports | None beyond SSH | Agent-to-master port |
| Drift correction | Only when you run it | Continuous, on each pull interval |
| Scale ceiling | SSH fan-out (mitigated by forks/pull) | Master handles many agents natively |
Agentless wins on simplicity and security surface. Agent-based wins on continuous enforcement — it keeps correcting drift without anyone kicking off a run. Many teams pick Ansible precisely because the simplicity outweighs the lack of a constant background loop, and they schedule runs through CI instead.
Common Mistakes to Avoid
- Using
commandorshellfor everything. Raw commands are not idempotent and defeat the point of Ansible. Reach for a real module (apt,copy,service) first. - Forgetting
become: true. Tasks that need root silently fail or error without privilege escalation. - Disabling fact gathering, then using facts. If a task references
ansible_factsbut you setgather_facts: false, it will fail. - Treating the control node as special infrastructure. It holds no state between runs; any machine with SSH access and Ansible installed can be a control node.
- Assuming push means continuous. Ansible only enforces state when you run it. If you need continuous drift correction, schedule the runs.
FAQ
Does Ansible need an agent on managed nodes?
No. Managed nodes only need an SSH server and a Python interpreter, both of which ship on most Linux distributions. Ansible copies modules over SSH, runs them, and removes them, so nothing is permanently installed.
What is the difference between a play and a playbook?
A play maps one group of hosts to an ordered list of tasks. A playbook is a YAML file that contains one or more plays, letting you configure different host groups in different ways within a single run.
Why does idempotency matter?
Idempotency means re-running a playbook does not cause harm or unintended changes — tasks only act when the system is not already in the desired state. This lets you run the same playbook repeatedly and safely, which is essential for reliable automation.
Is push or pull better?
Neither is universally better. Push (Ansible's default) is simpler and runs on demand with no agents. Pull (agent-based tools) enforces state continuously on a schedule. Teams that value simplicity and a smaller attack surface tend to prefer push and trigger runs through CI.