Variable Lookup Order
Ansible’s official documentation lists over twenty distinct precedence levels for variables. Memorizing that list isn’t the goal of this chapter — it’s not really the goal of anyone’s Ansible journey, honestly. What you actually need is a practical mental model good enough to reason about the handful of sources you’ve learned in this section, plus a couple of tools for the moment you’re genuinely unsure which value is winning.
Why We’re Not Memorizing The Full List
Every variable source you’ve seen in this section — group vars, host vars, play vars, vars_files, facts, registered results — sits somewhere in that full precedence list, and a handful more sources you haven’t met yet sit alongside them. Trying to hold the exact ranking of all twenty-plus in your head while writing a playbook isn’t a realistic or useful way to work. What follows instead is a simplified, honest approximation, good enough for everyday use — with an explicit acknowledgment of where it’s a simplification, not the complete picture.
A Practical Mental Model
Roughly, from easiest to override to hardest:
- Group-level variables (
group_vars/, groupvars:in inventory) — the most general, easiest to override. - Host-level variables (
host_vars/, host-level inventory vars) — overrides group-level, for the reason you’d expect: more specific to that one host. - Facts — collected per host, sitting above host/group inventory vars, but still easily overridden by anything defined at the playbook level.
- Play-level variables (
vars:,vars_files) — this is the twist worth remembering explicitly: playbook-level definitions generally beat inventory-level ones, regardless of how specific the inventory-level source was. A genericvars:block on a play can override a very specifichost_varsfile for that exact host — “specificity” here is about where in the playbook/inventory structure something is defined, not purely about host-versus-group. - Registered variables (
register,set_facts) — set during execution, and from that point on, they override earlier values for that host. - Extra vars (
-eon the command line) — covered next, and always wins over everything above.
Seeing It In Action
Define the same variable at three different levels at once, and watch which one actually shows up:
stage: "from group_vars"stage: "from host_vars"- name: Lookup Order Demo
hosts: ubuntu
vars:
stage: "from play vars"
tasks:
- name: Print stage
ansible.builtin.debug:
msg: "{{ stage }}"ok: [ubuntu] => {
"msg": "from play vars"
}Even though host_vars/ubuntu.yaml is about as specific as an inventory-level definition can get — naming this exact host — the playbook’s own vars: still wins. This is exactly the nuance flagged in item 4 above, made concrete.
The Ultimate Override: Extra Vars (-e)
Pass a variable directly on the command line, and it beats every source covered so far, no exceptions:
ansible-playbook -i inventory.yaml lookup_demo.yaml -e "stage=from_extra_vars"ok: [ubuntu] => {
"msg": "from_extra_vars"
}This is genuinely useful, not just a trivia fact — -e is the standard way to override a value temporarily, for a single run, without editing any file at all. Testing a different configuration value for one run, without committing to changing it everywhere, is exactly what -e is for.
What To Do When You’re Not Sure Which Value Is Winning
Rather than trying to reason through the full precedence list abstractly, two practical tools settle it directly:
ansible.builtin.debug: var: your_variable, placed right at the point of confusion in your playbook, shows you the actual resolved value at that exact moment — no guessing required.ansible-inventory --host <hostname>shows the merged inventory-level variables (group vars and host vars combined) Ansible resolves for a specific host, letting you check that layer in isolation, separately from whatever a playbook’s ownvars:might be doing on top of it.
ansible-inventory --host ubuntuBest Practices
- Don’t memorize the full precedence list — use the simplified model above for everyday reasoning, and reach for
debugoransible-inventorythe moment you need certainty instead of a guess. - Avoid defining the same variable at multiple levels in the first place, exactly as recommended in the earlier
vars_files-vs-varsgotcha — the best way to avoid precedence confusion is to not create it. - Use
-efor genuine one-off overrides during testing, not as a permanent way to configure something that should really live in a vars file or inventory.