Playbooks, Plays, and Tasks¶
An Ansible playbook is a YAML file containing one or more plays. A play targets a group of hosts and runs an ordered list of tasks on them, and each task calls one module. So the difference between a play and a playbook: the play is one "these hosts get these tasks" block, and the playbook is the file that holds one or more of them. You run a playbook with ansible-playbook -i inventory.ini site.yml.
What You'll Learn¶
- The three-level structure: playbook → play(s) → task(s)
- The keywords that decide what runs, on which hosts, in what order
- How this scales past a single flat task list
Mental Model¶
flowchart TD
P[Playbook: site.yml] --> Play1[Play: Configure web servers]
P --> Play2[Play: Configure db servers]
Play1 --> T1[Task: Install nginx]
Play1 --> T2[Task: Deploy config]
Play1 --> T3[Task: Start service]
Play2 --> T4[Task: Install postgresql]
Play2 --> T5[Task: Initialize database]
- A playbook is a YAML file containing one or more plays.
- A play maps a group of hosts to a set of tasks (and optionally roles) — it's the unit that has its own
hosts:,become:, andvars:. - A task calls exactly one module with specific arguments.
Minimal Example¶
---
- name: Configure web servers
hosts: web
become: true
tasks:
- name: Install nginx
ansible.builtin.package:
name: nginx
state: present
Practical Example — Multiple Plays, Ordering Keywords¶
---
- name: Preflight checks on every host
hosts: all
tasks:
- name: Refuse to continue if / has less than 2 GB free
ansible.builtin.assert:
that: (ansible_facts['mounts'] | selectattr('mount', 'equalto', '/') | first).size_available > 2 * 1024**3
fail_msg: "Less than 2 GB free on / — clean up before deploying"
- name: Configure web servers
hosts: web
become: true
pre_tasks:
- name: Refresh the apt cache if it's older than an hour
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600
when: ansible_facts['os_family'] == 'Debian'
roles:
- nginx
post_tasks:
- name: Confirm nginx responds
ansible.builtin.uri:
url: "http://localhost"
status_code: 200
- name: Configure database servers
hosts: db
become: true
roles:
- postgresql
- The preflight play checks a real condition and fails with a clear message. Running
df -hand registering the output would never stop anything. pre_tasks/post_tasksrun before/afterroles:, regardless of what's inside the role — useful for "always check X before this role runs" logic that shouldn't live inside the role itself.update_cachebelongs to the distribution-specific module (apt,dnf). The genericpackagemodule only installs and removes packages.- Plays run in order, top to bottom, each against its own
hosts:pattern. - Within a play, by default (the
linearstrategy), Ansible runs each task on all targeted hosts before moving to the next task — not host-by-host sequentially. See Forks, Serial, Strategy.
Key Play-Level Keywords¶
| Keyword | Purpose |
|---|---|
hosts |
Target pattern (see Inventory) |
become |
Escalate privilege for this play's tasks |
vars |
Play-scoped variables |
gather_facts |
Whether to run the implicit setup module first (default: true) |
pre_tasks / tasks / post_tasks |
Ordered task groups around roles: |
serial |
Batch size for rolling execution — see Advanced Execution |
strategy |
linear (default) vs. free — see Advanced Execution |
Common Mistakes¶
- Assuming tasks run host-by-host from top to bottom — the default is task-by-task, across the whole batch of hosts, which surprises people used to imperative scripting.
- Putting everything in one giant play instead of splitting by host group and responsibility.
- Setting
gather_facts: true(the default) on plays that never use facts — wasted time on every run at scale. See Performance.
Interview Questions¶
- What's the difference between a playbook, a play, and a task?
- What do
pre_tasksandpost_taskslet you do thattasksalone can't? - Does Ansible run task-by-task or host-by-host by default?
Next¶
Continue to Modules — the thing every task actually calls.