YAML essentials
YAML uses spaces to represent nesting; tabs are not valid indentation. A list begins with -, and a mapping uses key: value. Block scalar | preserves line breaks, while > folds most line breaks into spaces. # starts a comment, and --- marks the start of a document.
Quote values that YAML might misread: yes, no, on, and off can become Booleans, 08:30 may be parsed in surprising ways, and a value starting with { or * needs quotes. When in doubt, quote strings.
flowchart LR PB["site.yml<br/>playbook"] --> PL["Play<br/>hosts: web"] PL --> T1["Task<br/>apt: nginx"] T1 --> T2["Task<br/>template: nginx.conf"] T2 -- "notify" --> H["Handler<br/>restart nginx"] PB -. "--check --diff" .-> DRY["Dry run<br/>preview changes"] %% step PB: A playbook is a YAML list of plays. %% step PL: Each play maps a group of hosts to the tasks they should run. %% step T1: Tasks run in order, each calling one module. %% step T2: A task that changes a config file can notify a handler. %% step H: Handlers run once at the end, only if something notified them. %% step DRY: --syntax-check, --check and --diff let you review before changing hosts.
Playbook structure
---
- name: Configure web hosts
hosts: web
become: true
vars:
site_dir: /var/www/demo
tasks:
- name: Install nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
- name: Deploy config
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/default
notify: Reload nginx
handlers:
- name: Reload nginx
ansible.builtin.service:
name: nginx
state: reloaded
A play selects hosts; tasks is an ordered list. Put become: true at play or task level when an operation needs root. FQCNs such as ansible.builtin.apt make a module's origin explicit. Give every task a name, since it appears in the output.
Handlers run only when a task that notifies them reports changed, and only once at the end of the play. That is how a service is reloaded after its config really changes, not on every run. A playbook may contain several plays, for example one for db and one for web.
Running safely
| Command | Goal |
|---|---|
ansible-playbook --syntax-check site.yml |
Parse without running tasks |
ansible-playbook -i inventory.ini site.yml --check --diff |
Predict changes and show file differences |
ansible-playbook -i inventory.ini site.yml --limit web1 |
Run on one host first |
ansible-playbook -i inventory.ini site.yml --tags nginx |
Run only tagged tasks |
ansible-playbook -i inventory.ini site.yml |
Execute for real |
Check mode cannot guarantee zero side effects for every module and does not replace review. -v to -vvvv add detail when a task fails, and ansible-lint flags common style and safety problems.
Reading the recap
The final PLAY RECAP counts ok, changed, failed, unreachable, and skipped per host. A rerun should tend toward changed=0 after desired state is reached; a task that reports changed every time usually needs a better module or guard.