unit 4 · Playbooks
YAML pitfalls & the quoting rule
The parser gets your file before Ansible does
you may want to read 12 · Playbook anatomy first — but nothing is locked
tl;dr
- Indentation is structure, and it is spaces only — a tab is a parse error, not a style choice.
- A value that STARTS with {{ }} must be quoted, because a bare { opens YAML inline-dict syntax.
- Unquoted yes/no/true/false all become booleans — quote them when you mean the literal word.
why it matters YAML slips are among the most common beginner failures — tabs, unquoted {{ }}, surprise booleans and stray colons all kill a playbook before Ansible reads a single task.
Lesson 12 gave you a working playbook. This lesson covers the four ways YAML quietly wrecks the next one. None are Ansible bugs: a YAML parser reads your file before Ansible sees a single task, and applies YAML’s rules — not Ansible’s. Learn where those rules bite and you’ve pre-empted most beginner syntax errors.
1. Spaces, never tabs
In YAML, indentation is the structure — no braces mark where a block
starts and ends, only depth. Two-space indent is the convention, and it
must be spaces: one tab anywhere is a hard parse error. Configure your
editor now (spaces for .yml, render whitespace) and this trap disappears.
2. The quoting rule
Ansible’s placeholder for “a variable’s value goes here” is {{ var }}.
The full templating machinery is lesson 23; today it’s just a placeholder.
Here’s the trap every newcomer hits:
- name: Deploy the config
ansible.builtin.copy:
src: {{ config_file }} # bare { — YAML chokes here
dest: /etc/app.confWhy: in YAML, { opens an inline flow mapping — {key: value}, the
same brace syntax JSON uses. The parser reads {{ config_file }} as a dict
inside a dict, finds no key: value shape, and dies with an error that
never mentions quoting. The fix costs two characters:
src: '{{ config_file }}'
# mid-string needs no quotes — the value doesn't START with {
dest: /etc/{{ app_name }}.confThe rule is precise: a value that starts with {{ must be quoted.
Braces mid-string open as plain text and parse fine. One preview so the
rule doesn’t overreach: when: takes a bare expression with no {{ }} at
all — that’s lesson 18.
3. The yes that became True
Unquoted yes, no, true and false all parse as booleans — not
strings:
vars:
enable_firewall: true # boolean — what you meant
cleanup: yes # ALSO a boolean — same as true
country_code: 'no' # quoted — stays the string "no"The surprise cuts both ways. Older playbooks are full of become: yes —
it works and means exactly true. The nastier direction is silent data
loss: an unquoted no meant as a country code becomes false, with no
error at all.
4. Colons and braces inside strings
The last trap: a colon-plus-space inside a value looks like a new
key: value pair to the parser.
- name: Deploy: production release # second ": " = parse error
- name: 'Deploy: production release' # quoted — one stringSame family as the quoting rule: :, { and [ are live YAML syntax, so
a string containing them needs quotes. When in doubt, quote — it is never
wrong. Single quotes are fully literal; double quotes process escapes like
\n, occasionally what you want and usually not.
That’s the whole minefield: tabs, leading {{, bare booleans, embedded
colons. Each fails (or worse, succeeds) before a single module runs.
That is why ansible-playbook --syntax-check is worth running the moment a
playbook looks done. Next lesson: what separates a module from a plugin
from a collection.
practice — this task fails to parse. why? (1/4)
- name: Deploy the config
ansible.builtin.copy:
src: {{ config_file }}
dest: /etc/app.confpractice, not progress — the four traps from this lesson
self-check — recall, not a test
1 Why does src: {{ config_file }} fail before Ansible even runs?
2 When does a value containing {{ }} NOT need quotes?
3 You write country_code: no and nothing errors. What did you actually store?
lab (optional — never required)
Write a task with an unquoted {{ }} value, run ansible-playbook --syntax-check to read the real error, then quote the value and watch the same command pass.
needs: The control node from lesson 5.