Skip to main content
RunBook Academy

AnsibleIV · Inventory FundamentalsInventory inspection

More than one inventory source

Intermediate⏱ ~20 minbash

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

Not yet marked complete on this device.

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
Read-only / Safea directory of three files, merged
$ ansible-inventory -i inventory --graph
@all:
|--@ungrouped:
|--@web:
|  |--web01.example.com
|--@canary:
|  |--canary01.example.com

The 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.

Read-only / Safecore first, overrides second
$ 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:

Read-only / Safeoverrides first, core second
$ 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:

Read-only / Safethe ignore list
$ ansible-config dump | grep INVENTORY_IGNORE_EXTS
INVENTORY_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:

Read-only / Safeone broken file among several
$ 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.com

The 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:

Read-only / Safemake an unparseable source fatal
ANSIBLE_INVENTORY_ANY_UNPARSED_IS_FAILED=true \
ansible-inventory -i inventory --graph

INVENTORY_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

  1. Q1. Two inventory files both set `pool_size` for web01. The command is `-i 10-core.yml -i 20-overrides.yml`. Which value applies?

  2. Q2. An inventory file that no plugin can parse produces a warning, and the command still exits 0.

  3. Q3. A directory is used as the inventory source. Which of these files in it are read as inventory? Select all that apply.

  4. 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.