Skip to content

Ansible: variable precedence

Mental model

Ansible has ~22 precedence levels but you only need the bookends: role defaults lose, extra vars (-e) win. Everything else falls in between in a predictable gradient from general to specific.

What it looks like

Variables appear to come from nowhere or change unexpectedly. People chase values through five files before finding the source. The official precedence list is intimidating.

What it really is

Key levels from lowest to highest priority:

  1. Role defaults (roles/x/defaults/main.yml) -- lowest
  2. Inventory group_vars/
  3. Inventory host_vars/
  4. Play vars:
  5. Role vars/ (roles/x/vars/main.yml)
  6. Task vars:
  7. set_fact / register
  8. Extra vars (-e) -- highest, always wins

The full list has ~22 entries but most are edge cases (e.g., include_vars, vars_prompt, vars_files each have their own slot). In practice the above 8 cover almost every situation.

Why it seems confusing

  • Too many levels with subtle ordering
  • role defaults/ vs role vars/ sounds like the same thing but they sit at opposite ends of the precedence spectrum
  • Variables can be set in inventory, playbooks, roles, tasks, facts, and CLI -- all at once

What actually matters

The simplified rule: defaults < inventory vars < play/role vars < extra vars

Design intent: - Role defaults/ = safe fallbacks anyone can override - Role vars/ = internal constants the role needs (hard to override on purpose) - Extra vars (-e) = CLI emergency override, always wins

Put tunable knobs in defaults/. Put internal constants in vars/. Let users override via group_vars/ or -e.

Common mistakes

  • Putting user-tunable values in roles/x/vars/ instead of roles/x/defaults/ -- makes them nearly impossible to override from inventory
  • Setting the same variable in multiple places without knowing which one wins
  • Forgetting that set_fact overrides almost everything except extra vars
  • Assuming inventory group_vars beat play vars (they do not)

Small examples

# roles/nginx/defaults/main.yml  -- lowest priority
nginx_port: 80

# group_vars/production.yml  -- overrides role default
nginx_port: 8080

# play vars  -- overrides group_vars
- hosts: webservers
  vars:
    nginx_port: 9090

# CLI extra vars  -- overrides everything
# ansible-playbook site.yml -e nginx_port=443

Result with all four set: nginx_port = 443 (extra vars win).

One-line summary

Role defaults are the bottom, extra vars (-e) are the top; put tunables in defaults, use -e for emergency overrides.