"Why is this variable not the value I set?" is the most common Ansible debugging question.
Ansible merges variables from over twenty sources. You don't need the whole list, just the landmarks, from lowest to highest:
1. role defaults/ 2. inventory variables: group vars (all, then parent groups, then child groups), then host vars; files in group_vars//host_vars/ beat variables written inside the inventory file 3. facts and cached facts 4. play vars, vars_prompt, vars_files 5. role vars/ and include_vars 6. block and task vars 7. set_fact and register 8. role parameters and include_role params 9. extra vars (-e), which always win.
Group order: when a host is in two sibling groups, the group whose name sorts later alphabetically wins, unless you set ansible_group_priority. Relying on that is fragile; avoid defining the same variable in sibling groups.
Dictionaries are replaced, not merged, by default: a host-level nginx: {port: 81} wipes the group-level nginx: {port: 80, workers: 4}. Prefer flat, prefixed variables (nginx_port), or merge explicitly with the combine filter.
Practical rules: put defaults in role defaults, environment and group values in group_vars, exceptions in host_vars, and use -e only for one-off overrides. To see the result, ansible-inventory --host web1 or a debug: var=... task.
Example
# roles/app/defaults/main.yml
app_port: 8080
app_settings: {workers: 4, log_level: info}
# inventory/group_vars/prod.yml
app_port: 80
app_settings: {log_level: warn} # replaces the whole dict: workers is gone
# better: merge explicitly in the role
# app_effective: "{{ app_settings_default | combine(app_settings_override | default({})) }}"
# ansible-inventory -i inventory --host web1 | grep -A3 app_
# ansible-playbook site.yml -e app_port=9000 # extra vars beat everythingRemember
- Role defaults lowest, extra vars highest; host vars beat group vars
- Sibling groups: later alphabetical wins unless ansible_group_priority
- Dicts are replaced, not merged; prefer flat vars or combine
Watch out for
- Overriding one key of a dictionary variable and silently losing all its other keys.