AnsibleIV · Inventory FundamentalsInventory mechanics
group_vars and host_vars on disk
What you'll learn
- Lay out group_vars/ and host_vars/ as files or as directories, and predict the read order
- State which of the two search paths wins when both define the same variable
- Diagnose an ignored variable using ansible-inventory --host and a debug task together
- Explain why ansible-inventory --host can disagree with what a playbook actually sees
Prerequisites
Verified against ansible-core 2.21.x · ansible (community package) 14.x · Python (controller) 3.12+ · ansible-lint 26.x · Molecule 26.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-11
Variables set inside the inventory file work and do not scale. Once a group has more than a handful of settings, or any of them is a list or a dictionary, the inventory becomes unreadable and the merge conflicts become constant.
group_vars/ and host_vars/ are the answer: one file per group or
host, loaded automatically, no wiring required. The mechanism is
straightforward. The part that causes incidents is where Ansible looks
for them, because it looks in two places and does not tell you which
one it used.
The layout
A file per group, named for the group:
estate/
├── inventory/
│ └── hosts.yml
├── group_vars/
│ ├── all.yml
│ ├── webservers.yml
│ └── databases.yml
├── host_vars/
│ └── db01.example.com.yml
└── playbooks/
└── site.yml
group_vars/all.yml applies to every host, because every host is in
all. group_vars/webservers.yml applies to the members of
webservers, including any host that is a member through nesting.
host_vars/db01.example.com.yml applies to that host alone, and the
filename must match the inventory name exactly — if the inventory says
db01.example.com, a file called db01.yml is read by nothing.
The extension may be .yml, .yaml, .json, or absent entirely.
Ansible tries them; consistency within a repository matters more than
which one you pick.
A directory instead of a file
Any of those files can be a directory, and every file inside it is read:
group_vars/
└── webservers/
├── 10-network.yml
├── 20-application.yml
└── 30-secrets.yml
This is the right shape once a group has more than about thirty lines of
settings, and it is the standard place to isolate vault-encrypted
content — 30-secrets.yml can be encrypted while its siblings stay
readable and reviewable in a pull request.
Files inside the directory are read in lexicographic order, and
later files override earlier ones for the same key. Hence the numeric
prefixes: 10-, 20-, 30- make the order explicit rather than an
accident of what people named things.
The two search paths
Here is the part that produces real incidents.
Ansible looks for group_vars/ and host_vars/ relative to the
inventory file, and relative to the playbook file. Both. Every
run.
estate/
├── group_vars/
│ └── web.yml <-- playbook-adjacent
├── inventory/
│ ├── hosts.yml
│ └── group_vars/
│ └── web.yml <-- inventory-adjacent
└── show.yml
Two files, the same name, both valid, both loaded. When they define the same variable, one wins — and it is the playbook-adjacent one.
Here is that arrangement executed. inventory/group_vars/web.yml
contains served_by: inventory-adjacent and inventory_only;
group_vars/web.yml next to the playbook contains served_by: playbook-adjacent and playbook_only.
$ ansible-playbook -i inventory/hosts.yml show.ymlPLAY [Show which group_vars won] ***********************************************
TASK [Print the contested variable] ********************************************
ok: [localhost] => {
"msg": "served_by=playbook-adjacent inventory_only=yes-only-here playbook_only=yes-only-here"
}
PLAY RECAP *********************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0Both files were read. The playbook-adjacent value won the contested key, and every key defined in only one of them came through. This is a merge with a precedence rule, not a replacement — which is why the situation survives so long undetected. Most keys are uncontested, so most of both files appears to work.
$ ansible-inventory -i inventory/hosts.yml --host localhost{
"ansible_connection": "local",
"inventory_only": "yes-only-here",
"served_by": "inventory-adjacent"
}Two commands, one repository, one moment in time, two different answers.
Neither is lying. ansible-inventory is telling you the truth about the
inventory, and the playbook is telling you the truth about the run.
The operational rule follows directly: ansible-inventory --host is
authoritative for inventory variables and nothing else. When a
variable is not what you expect, use both — and a difference between
them is itself the diagnosis.
Diagnosing “the variable I set is being ignored”
The symptom is always the same and the cause is usually one of four things. Work them in this order, because it is cheapest first.
ansible-inventory -i inventory/hosts.yml --host web01.example.comansible -i inventory/hosts.yml web01.example.com \
-m debug -a 'var=my_variable' --connection=localgrep -rn 'my_variable' group_vars/ host_vars/ inventory/ playbooks/ roles/The four causes, in the order you will meet them:
- A second
group_varstree, playbook-adjacent, overriding the inventory-adjacent one. Steps 1 and 2 disagree. - Two groups both setting it. Steps 1 and 2 agree with each other and disagree with you. The last lesson in this part owns this.
- Something later in the precedence chain — a play
vars:, a role default being overridden,-eon the command line. Ansible’s full variable precedence order is a subject of its own later in the course; for now, know thatgroup_varsis near the bottom of it and is easy to override by accident. - A filename that does not match.
host_vars/db01.ymlfor an inventory host nameddb01.example.comis read by nothing at all, silently. Step 3 finds this one instantly, which is why it is worth running even when you are sure.
Knowledge check
Knowledge check · 4 questions
Q1. A repository has group_vars/web.yml beside the inventory and another beside the playbook, both setting `app_port`. Which value does the play use?
Q2. `ansible-inventory --host web01` shows every variable the playbook will see for that host.
Q3. A variable set in group_vars/webservers.yml has no effect on web01. Which of these could explain it? Select all that apply.
Q4. A playbook is moved from site.yml at the repository root into playbooks/site.yml. Nothing else changes, and some variables stop taking effect. Why?
Passing score: 75%. Answers are checked in this browser.