Skip to content

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:

  1. Group-level variables (group_vars/, group vars: in inventory) — the most general, easiest to override.
  2. Host-level variables (host_vars/, host-level inventory vars) — overrides group-level, for the reason you’d expect: more specific to that one host.
  3. Facts — collected per host, sitting above host/group inventory vars, but still easily overridden by anything defined at the playbook level.
  4. 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 generic vars: block on a play can override a very specific host_vars file for that exact host — “specificity” here is about where in the playbook/inventory structure something is defined, not purely about host-versus-group.
  5. Registered variables (register, set_facts) — set during execution, and from that point on, they override earlier values for that host.
  6. Extra vars (-e on 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:

group_vars/servers.yaml
stage: "from group_vars"
host_vars/ubuntu.yaml
stage: "from host_vars"
lookup_demo.yaml
- 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 own vars: might be doing on top of it.
ansible-inventory --host ubuntu

Best Practices

  • Don’t memorize the full precedence list — use the simplified model above for everyday reasoning, and reach for debug or ansible-inventory the 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-vars gotcha — the best way to avoid precedence confusion is to not create it.
  • Use -e for genuine one-off overrides during testing, not as a permanent way to configure something that should really live in a vars file or inventory.
Last updated on