← all cheat sheets
FUNDAMENTALS · MECHANISM WALKTHROUGH

Ansible Playbook Run
What Really Happens, Step by Step

from `ansible-playbook site.yml` to a host actually changing — every step, in order.
PARSE PLAYBOOK BUILD INVENTORY CONNECT (SSH) GATHER FACTS PUSH MODULE EXECUTE RETURN JSON NEXT TASK/HOST
01 The Full Sequence — Worked Example
1

Parse the Playbook

Ansible reads site.yml top to bottom, parses the YAML into an internal list of plays, and validates syntax before touching any host. A syntax error here means zero hosts get touched — this is a fully pre-flight step.

Parse
2

Build the Target Host List

The play's hosts: value is matched against the inventory (static file, dynamic script, or -i override). Patterns like web:&!staging get resolved into a concrete list of hostnames before anything connects.

Inventory
3

Connect Over SSH (or WinRM)

Ansible is agentless — no daemon runs persistently on the managed node. For each host, it opens an SSH connection using the configured user/key, exactly like you'd do manually. Multiple hosts are processed in parallel, bounded by the forks setting (default 5).

Connect
4

Gather Facts

Unless gather_facts: false, Ansible's first real action on the host is running the setup module — a script that inspects OS, IP addresses, disk, memory, etc., and returns it as JSON. These facts become variables (ansible_facts) usable in every later task.

Facts
5

Checkpoint — Before Any Task Runs

By this point Ansible has: validated syntax, resolved the host list, opened a connection, and gathered facts. Nothing on the managed node has changed yet — everything so far is read-only reconnaissance.

Checkpoint
6

Render the Task's Module + Arguments

For the first task, Ansible takes the module (e.g. apt) and its arguments, substitutes any Jinja2 variables ({{ }}), and assembles a self-contained Python script with the resolved arguments embedded.

Render
7

Copy the Module to a Temp Directory

That assembled script is copied over SSH (SFTP/SCP) to a temp path on the managed node, e.g. ~/.ansible/tmp/. This is why Ansible needs write access to the remote home directory, not just root privilege for the task itself.

Transfer
8

Execute the Module Remotely

The remote Python interpreter runs the uploaded script. If become: true is set, it's re-invoked through sudo (or another privilege escalation method) first. The module does its actual work — installing a package, writing a file, starting a service.

Execute
9

Module Returns JSON

Every module — no exceptions — returns a JSON blob over stdout: at minimum changed (true/false), failed, and any module-specific data. This is the single mechanism Ansible uses to know what happened; there's no separate side-channel.

Result
10

Temp File Cleanup

The uploaded module script is deleted from the managed node (unless ANSIBLE_KEEP_REMOTE_FILES=1 is set for debugging), leaving no persistent footprint.

Cleanup
11

Handlers Queue (If Notified)

If the task used notify: and reported changed: true, the named handler is queued — but not run yet. Handlers only fire once, at the end of the play, after every task has been attempted.

Notify
12

Repeat Per Task, Per Host

Steps 6-11 repeat for every task in the play, and the whole per-host sequence runs in parallel across the host batch (bounded by forks), before Ansible moves to the next batch.

Loop
13

Handlers Run, Play Recap Printed

Any queued handlers execute once at the end. Ansible then prints the PLAY RECAP — ok/changed/unreachable/failed counts per host — which is the actual source of truth for whether the run succeeded.

Finish
02 How to Explain This in an Interview
03 Follow-Up / Gotcha Questions
Q Does Ansible require an agent installed on managed nodes?
A No — that's the defining design choice. It only needs SSH (or WinRM for Windows) and a Python interpreter already present on the target; the "agent" is a temporary script pushed and removed per task.
Q What happens if the SSH connection drops mid-task?
A The task fails for that host and is marked unreachable/failed in the recap; other hosts in the batch are unaffected. Ansible doesn't retry automatically unless you wrap it in retries/until.
Q Why does a task sometimes need both SSH access and become: true?
A SSH access is what lets the module get uploaded and run at all; become is a separate, later privilege escalation for the module's actual execution — you can have one without the other.
Q What if two tasks in the same play target overlapping hosts but run at different fork counts effectively?
A Forks apply per-task, not per-play — Ansible processes the host batch through step 6-11 in lockstep for each task, so a slow host at step 8 can hold up the batch before the next task starts.
Q Does the play recap "ok" count include tasks with changed: true?
A Yes — "ok" and "changed" aren't mutually exclusive in the recap; changed is a subset shown separately. A task only avoids "ok" entirely if it fails, is skipped, or the host is unreachable.
Q Where does the module's Python code actually execute — on the control node or the managed node?
A On the managed node. The control node only assembles and transfers the script; interpretation, filesystem changes, and the changed/failed decision all happen using the remote host's own Python interpreter.
04 Quick-Fire Glossary
TermMeaning
Control NodeThe machine running ansible-playbook itself — where inventory and playbooks live
Managed NodeA target host being configured — needs only SSH/WinRM + Python, no agent
PlayOne block in a playbook mapping a set of hosts to a set of tasks
TaskA single module invocation with specific arguments
forksMax number of hosts processed in parallel per task (default 5)
FactsHost attributes (OS, IP, memory) auto-gathered via the setup module
HandlerA task that only runs when notified, and only once, at the end of the play
becomePrivilege escalation (e.g. sudo) applied to a task's execution
changedThe JSON field a module returns to report whether it altered state
Play RecapThe final ok/changed/unreachable/failed summary printed per host