<aside> 🧭
Module 02 · Variables, Facts & Precedence
This is where mid-level and senior candidates get separated. Anyone can write a variable; far fewer can explain which of six definitions actually wins, and why.
🧠 concept → 🧪 exercise → ✅ expected result (hidden) → 🎯 interview questions (answers hidden)
Prerequisite: Module 01. You have already met ansible_distribution (Exercise C2.1) and inventory_hostname (Exercise D4.1) — this module explains what they actually are.
</aside>
<aside> 📖
Official docs: Using Variables · Templating (Jinja2)
</aside>
<aside> 🫙
The analogy. Think of a labelled jar in your kitchen. The jar says sugar on the front, and when you write the recipe you deliberately do not care what is inside it — you write "add the contents of the sugar jar" and move on. Someone else can fill that jar with white sugar or brown, and your recipe still works without a single edit.
That is the whole point of a variable, and it is why one playbook can serve both staging and production. The {{ }} braces mean "go and look inside this jar, and put whatever you find right here". Keep the jar in mind — it comes back in D5, when the jar labelled sugar turns out to be full of salt.
</aside>
Ansible variables are referenced with Jinja2 templating syntax — double curly braces:
vars:
http_port: 8080
app_name: checkout
tasks:
- name: Show the config
ansible.builtin.debug:
msg: "{{ app_name }} listens on port {{ http_port }}"
| Rule | Detail |
|---|---|
| Letters, digits, underscores only | app_port ✅ · app-port ❌ · app.port ❌ · app port ❌ |
| Must not start with a digit | 2nd_server ❌ · server_2 ✅ |
| Must not collide with a Python keyword | async, lambda, import are all invalid |
| Must not collide with a playbook keyword | environment, name, hosts — technically allowed but they shadow real keywords and cause baffling bugs |
<aside> ⚠️
Hyphens are the trap. app-port looks perfectly reasonable and is invalid — Jinja2 reads it as app minus port. You will not always get a clear error; sometimes you get a silent wrong value. Underscores, always.
</aside>
msg: {{ http_port }} # ❌ YAML sees a line starting with { and expects a dictionary
msg: "{{ http_port }}" # ✅ quote it
msg: "Port is {{ http_port }}" # ✅ fine either way, but quote for consistency
when: http_port == 8080 # ✅ NO braces here - when: is already a Jinja2 expression
when: "{{ http_port }} == 8080" # ⚠️ works but is wrong style, and ansible-lint flags it
<aside> 🔑
The rule in one line: if a value starts with {{, quote the whole thing. And never put {{ }} inside when:, because when: is evaluated as a Jinja2 expression already — the braces are redundant and can break on complex expressions.
</aside>
🧪 Exercise A1.1 — Break the quoting rule on purpose
---
- name: Quoting demo
hosts: localhost
gather_facts: false
vars:
http_port: 8080
tasks:
- name: This one fails
ansible.builtin.debug:
msg: {{ http_port }}