Skip to main content
RunBook Academy

AnsibleIV · Inventory FundamentalsInventory inspection

Proving what your inventory contains

Intermediate⏱ ~21 minbash

What you'll learn

  • Choose the right inventory inspection command for structure, variables, one host, or a pattern
  • Answer "which hosts is this group" from the command line rather than from memory
  • Recognise that a pattern matching nothing exits successfully, and plan for it
  • Build a pre-run verification habit that costs seconds and bounds every change

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.

This is the lesson the rest of the part exists to make possible.

You now know that groups nest, that membership inherits upward, that ranges expand into hosts nobody listed, and that two group_vars trees can both be read. Every one of those is a way for the inventory to resolve into something other than what the file appears to say.

So stop reading the file. Print the answer.

You do not know what hosts: all means until you have printed it.

Everything here is read-only. Nothing in this lesson connects to a managed node, which means all of it is safe to run at any time, on any machine, including during an incident.

Five commands, four questions

QuestionCommand
What is the group structure?ansible-inventory --graph
What variables does each host have?ansible-inventory --graph --vars
Everything, as machine-readable dataansible-inventory --list
What does this one host have?ansible-inventory --host <name>
Which hosts does this pattern reach?ansible <pattern> --list-hosts

The structure

Read-only / Safethe whole tree at once
$ ansible-inventory -i inventory/hosts.yml --graph
@all:
|--@ungrouped:
|--@cache:
|  |--cache01.example.com
|--@production:
|  |--@webservers:
|  |  |--web01.example.com
|  |  |--web02.example.com
|  |--@databases:
|  |  |--db01.example.com
|  |  |--db02.example.com

Restrict it to one group when the fleet is large:

Read-only / Safeone subtree
$ ansible-inventory -i inventory/hosts.yml --graph legacy
@legacy:
|--oldbox

The variables

Read-only / Safestructure and variables together
$ ansible-inventory -i inventory/hosts.yml --graph legacy --vars
@legacy:
|--oldbox
|  |--{ansible_host = 192.0.2.55}
|  |--{ansible_python_interpreter = /usr/bin/python3.9}
|  |--{ansible_user = root}

For one host, --host gives clean JSON that is easy to diff between two inventories or two branches:

Read-only / Safeone host, machine-readable
$ ansible-inventory -i inventory/hosts.ini --host edge1
{
  "ansible_host": "192.0.2.11"
}

--list gives the whole inventory in the same JSON form, and --list -y gives it as YAML, which is easier to read when you are checking structure rather than diffing:

Read-only / Safethe whole inventory as YAML
$ ansible-inventory -i inventory/hosts.ini --list -y
all:
children:
  ungrouped:
    hosts:
      jump01.example.com: {}
  webservers:
    hosts:
      web01.example.com: {}

The pattern

ansible-inventory answers questions about the inventory. --list-hosts answers the question you actually have before a change: which machines will this touch?

Read-only / Saferesolving a pattern without running anything
$ ansible -i inventory/hosts.ini 'all:!oddweb' --list-hosts
  hosts (10):
  web01.example.com
  web02.example.com
  web03.example.com
  web04.example.com
  db-a.example.com
  db-b.example.com
  db-c.example.com
  db-d.example.com
  edge1
  edge2

The count on the first line is the number to read. It is the blast radius of any play using that pattern, stated as an integer, before anything happens.

The same question can be asked of a playbook, which resolves its own hosts: line rather than requiring you to retype it:

Read-only / Safewhat this playbook would target
$ ansible-playbook -i inventory/hosts.ini --list-hosts playbooks/webtier.yml
playbook: playbooks/webtier.yml

play #1 (webservers): Configure the web tier	TAGS: []
  pattern: ['webservers']
  hosts (2):
    web01.example.com
    web02.example.com

This is the form to prefer, because it removes the transcription step. The pattern it reports is the one the play will actually use.

Both directions of wrong are silent

Here is the property that makes the habit necessary rather than merely sensible.

Read-only / Safea pattern with a typo in it
$ ansible -i inventory/hosts.yml nosuchgroup --list-hosts
[WARNING]: Could not match supplied host pattern, ignoring: nosuchgroup
[WARNING]: No hosts matched, nothing to do
hosts (0):

Exit code 0. A mistyped --limit in a scheduled job produces exactly this: a nightly run that succeeds, every night, having done nothing at all. Nothing alerts, because from the shell’s point of view the command worked.

And the opposite direction is worse, because it produces no warning at all. A pattern that matches three hundred hosts when you meant thirty also exits 0, also reports success, and does change three hundred machines. There is no warning for “matched more than you expected”, because Ansible has no idea what you expected.

The habit

Five steps, seconds to run, no connection to anything.

  1. Confirm which config file and which inventory are in effect: ansible --version | head -2, then ansible-config dump --only-changed.
  2. Print the group structure: ansible-inventory --graph. Look for hosts in ungrouped, and for nesting you did not expect.
  3. Resolve the pattern the playbook will use: ansible-playbook --list-hosts <playbook>. Read the count.
  4. Compare the count with what you believe it should be. A mismatch stops here, before anything runs.
  5. Spot-check one host you did not expect to see, or one you expected and cannot find: ansible-inventory --host <name>.

Step four is the one that matters, and it is the one people skip. The other four produce output; that one requires you to have an expectation and to check it. A count you read without comparing is a count you did not check.

Knowledge check

Knowledge check · 4 questions

  1. Q1. A nightly scheduled run has reported success for three weeks and changed nothing. The --limit value contains a typo. What did the operator see?

  2. Q2. Which command answers "which hosts will this playbook actually touch" with the least chance of transcription error?

  3. Q3. A variable whose value is a Jinja expression appears unevaluated in `ansible-inventory --list` output.

  4. Q4. `ansible-inventory --graph` shows a host in a group, but the run did not appear to touch it. Which explanations are Ansible behaving as instructed rather than a defect? Select all that apply.

Passing score: 75%. Answers are checked in this browser.