← All TILs · ansible

Inventory precedence: all, parent, child, host, and what ansible_group_priority really does

ansible - 2026-10-03

Ninth entry in the Ansible inventory from scratch series. Item 8 ranked the places a variable can live, with the inventory's group_vars/ and host_vars/ as the home of desired state. Inside the inventory, the same name can still be set for all, for several groups a host belongs to, and for the host itself. This entry peels one value back level by level to show which wins, then debugs a real conflict where the obvious fix, ansible_group_priority, does nothing. Everything below ran with ansible-core 2.21.4.

The inventory

db1 and db2 are in db, a child of postgresql, and in backup, a group next to postgresql: item 1 explains parent and child groups. Each group has a depth, its distance from all: all is 0, postgresql and backup are 1, db is 2. A group with several parents takes the longest path; ansible-core's inventory/group.py sets a child's depth to its parent's plus one, keeping the maximum.

One value, removed level by level

postgresql_max_connections was set in five places: all 10, postgresql 20, backup 40, db 30, host_vars/db1 50. The example read db1's value with ansible-inventory --host db1, removed the winning level, and read again:

Set in db1 got Removed next
all five 50 host_vars/db1
all, postgresql, backup, db 30 group_vars/db
all, postgresql, backup 20 group_vars/postgresql
all, backup 40 group_vars/backup
all 10

The order comes from one line in inventory/helpers.py: groups are sorted by (g.depth, g.priority, g.name), merged in that order, and the last one sets the value; host variables are applied after all groups.

Dicts are replaced, not merged

postgresql_conf, a dict, was {max_connections: 100, shared_buffers: 128MB} in all and {max_connections: 200} in postgresql. db2 got {"max_connections": 200}: shared_buffers was gone. Each level replaces the whole value. Part 3 of the data-shaping series shows the alternative: one variable per layer, merged explicitly with combine.

ansible_group_priority breaks ties, not depth

ansible_group_priority is a number on a group, 1 by default; a higher number merges later and wins. The docs say what it applies to: it "overrides the alphabetical sorting for the merge order for groups of the same level (after Ansible resolves the parent/child order)", and it can be set "only in an inventory source, not in group_vars/". With ansible_group_priority: 10 on backup, in the hosts file:

Priority is the second item of the sort key, after depth: it reorders groups of one depth, and nothing else.

A real conflict, and the fix that doesn't work

db1 is backed up, so it's in backup, whose group_vars set postgresql_wal_level: replica, the level WAL archiving needs. db sets postgresql_wal_level: minimal as the default for database servers. db1 got minimal, and backups would fail.

ansible-inventory --graph --vars, from item 6, shows where it comes from:

|--@postgresql:
|  |--@db:
|  |  |--db1
|  |  |  |--{postgresql_wal_level = minimal}
|  |  |--{postgresql_wal_level = minimal}
|--@backup:
|  |--db1
|  |  |--{postgresql_wal_level = minimal}
|  |--{postgresql_wal_level = replica}

Each group's own value is listed after its hosts, at the same indentation: backup says replica. The value nested under db1 is the host's merged value, the same under every group that lists it: minimal, from db, the deeper group.

Setting the value in host_vars/db1 would also have worked, for one host; the group structure fixes it for every backed-up server.

In short

The example repository

The series' companion repository, abdelhousni/ansible-inventory-series, holds these inventories. run.sh removes the winning level one at a time from a copy in its out/ directory, compares priority at the same depth and across depths, and prints the conflict before and after each fix, with the --graph --vars view. Its CI runs it on every push and compares the output with the expected one.

Sources

Created 2026-10-03T11:51:59+02:00 · Edit