AnsibleIV · Inventory FundamentalsInventory inspection
More than one inventory source
What you'll learn
- Combine inventory sources with repeated -i flags or with a directory
- Predict which value wins when two sources define the same variable
- Explain why a directory source is read alphabetically and which files are skipped
- Recognise that an unparseable source is a warning, not an error, and decide what to do about it
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
One inventory file stops being enough at a predictable point: when the estate has more than one environment, or when part of it comes from somewhere else — a cloud API, a CMDB, a team that maintains its own list.
Ansible accepts several sources at once and merges them into a single inventory. The merge is simple, and the two things that surprise people are the ordering rule and what a directory source quietly includes.
Two ways to supply more than one
Repeat -i:
ansible-playbook -i inventory/core.yml -i inventory/overrides.yml site.yml
Or point -i at a directory, and every valid source inside it is read:
inventory/
├── 10-core.yml
├── 20-overrides.yml
└── 30-extra.yml
$ ansible-inventory -i inventory --graph@all:
|--@ungrouped:
|--@web:
| |--web01.example.com
|--@canary:
| |--canary01.example.comThe directory form is usually the better one for a repository: adding a
source is adding a file rather than editing a command line, and the
ansible.cfg inventory setting points at one path that never changes.
Later wins
When two sources define the same variable for the same host, the later source wins. “Later” means later in the order the sources were supplied.
This is worth proving rather than trusting, because the direction is
easy to get backwards. 10-core.yml sets pool_size: 10;
20-overrides.yml sets pool_size: 99.
$ ansible-inventory -i inventory/10-core.yml -i inventory/20-overrides.yml --host web01.example.com{
"deploy_user": "deploy",
"pool_size": 99
}Now reverse the two flags and change nothing else:
$ ansible-inventory -i inventory/20-overrides.yml -i inventory/10-core.yml --host web01.example.com{
"deploy_user": "deploy",
"pool_size": 10
}Same files, same host, different result. The order of -i flags is a
functional part of the command, not a formatting choice.
For a directory, the order is alphabetical by filename. That is why
the numeric prefixes in the layout above are not decoration: 10-,
20-, 30- make the precedence explicit and stable. Without them, a
file renamed for unrelated reasons can silently change which value wins.
What a directory source skips
Not every file in an inventory directory is treated as inventory. Some extensions are ignored by default:
$ ansible-config dump | grep INVENTORY_IGNORE_EXTSINVENTORY_IGNORE_EXTS(default) = ['.pyc', '.pyo', '.swp', '.bak', '~', '.rpm', '.md', '.txt', '.rst', '.orig', '.cfg', '.retry']So a README.md and a notes.txt beside your inventory files are fine.
.cfg is in the list too, which is why an ansible.cfg inside an
inventory directory is not mistaken for a source.
Everything else is offered to the inventory plugins. A .yml, .yaml,
.json, or an extensionless file will be tried — and a file with no
extension that happens to be a shell script is offered to the script
plugin, which will execute it if it is executable.
An unparseable source is a warning
This is the behaviour most likely to cost you an evening, so it is worth seeing exactly.
Put a file with broken YAML in an inventory directory:
$ ansible-inventory -i inventory --graph[WARNING]: Failed to parse inventory with 'auto' plugin: YAML parsing failed: While parsing a flow node did not find expected node content.
Origin: /home/ops/estate/inventory/broken.yml:3:1
[WARNING]: Failed to parse inventory with 'yaml' plugin: YAML parsing failed: While parsing a flow node did not find expected node content.
[WARNING]: Failed to parse inventory with 'ini' plugin: Failed to parse inventory: Invalid host pattern 'all:' supplied, ending in ':' is not allowed, this character is reserved to provide a port.
[WARNING]: Unable to parse /home/ops/estate/inventory/broken.yml as an inventory source
@all:
|--@ungrouped:
|--@web:
| |--web01.example.comThe graph renders. The exit code is 0. Whatever hosts were in
broken.yml are simply not in the inventory, and the run that follows
will succeed against a smaller fleet than intended.
Notice the shape of the warnings, because it explains a lot of confusing output: Ansible offers the file to each enabled plugin in turn, so a broken YAML file also produces a complaint from the INI plugin about host patterns. The last message before the source is abandoned is often the least relevant one.
Two settings change this, and both default to off:
ANSIBLE_INVENTORY_ANY_UNPARSED_IS_FAILED=true \
ansible-inventory -i inventory --graphINVENTORY_ANY_UNPARSED_IS_FAILED fails when any source cannot be
parsed. INVENTORY_UNPARSED_IS_FAILED fails only when every source
fails, which is a much weaker guarantee and is not what you want.
Knowledge check
Knowledge check · 4 questions
Q1. Two inventory files both set `pool_size` for web01. The command is `-i 10-core.yml -i 20-overrides.yml`. Which value applies?
Q2. An inventory file that no plugin can parse produces a warning, and the command still exits 0.
Q3. A directory is used as the inventory source. Which of these files in it are read as inventory? Select all that apply.
Q4. The command is `-i base.yml -i extras/`, and extras/ contains 01-first.yml. When is 01-first.yml processed?
Passing score: 75%. Answers are checked in this browser.