Skip to main content
RunBook Academy

AnsibleXXX · Host Targeting and Blast RadiusNarrowing a run

Verifying the effective host list before you run

Intermediate⏱ ~27 minbash

What you'll learn

  • Produce the exact host list a given command will act on, before running it
  • Choose the authoritative verification command rather than a convenient one
  • Avoid the --graph trap, which reports inventory contents rather than run scope
  • Verify the other half of scope with --list-tasks and --list-tags

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 skill the part exists to teach.

Before you run something that changes hosts, determine exactly which hosts it will change — from the command line, not from belief.

It takes about four seconds, connects to nothing, and is the only control that catches every targeting mistake in this part at once: a mistyped group, an exclusion that did not fire, a glob that reached staging, a dynamic source that grew, a --limit that intersected to nothing.

The commands are simple. The discipline is running the right one and comparing the count against a number you decided beforehand.

The authoritative command

Read-only / Safethe one to learn
ansible-playbook -i inventory/ site.yml --limit 'us_east:!canary' --list-hosts

It is authoritative because it accounts for both halves of the target: the play’s own hosts: line, and your --limit. Every other form accounts for one of them.

Read-only / Safea two-play playbook under one limit
$ ansible-playbook -i inventory/hosts.yml site.yml --limit eu_west --list-hosts
playbook: site.yml

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

play #2 (db): Configure the database tier	TAGS: []
  pattern: ['db']
  hosts (1):
    db01.example.com

Read three things from that output:

  • The pattern: line is the play’s own hosts: value, unmodified.
  • The hosts (N): count is the effective blast radius for that play.
  • The number of plays — a site.yml with nine plays has nine separate answers, and the total is their union, not any single one.

The trap: --graph ignores --limit

This one has a specific name because it catches people who are doing the right thing.

Read-only / Safethe limit is silently discarded
$ ansible-inventory -i inventory/hosts.yml --graph --limit canary
@all:
|--@ungrouped:
|--@production:
|  |--@web:
|  |  |--web01.example.com
|  |  |--web02.example.com
|  |  |--web03.example.com
|  |  |--web04.example.com
|  |--@db:
|  |  |--db01.example.com
|  |  |--db02.example.com
|  |--@cache:
|  |  |--cache01.example.com
|--@staging:
|  |--@web_stage:
|  |  |--stage-web01.example.com
|  |  |--stage-web02.example.com
|  |--@db_stage:
|  |  |--stage-db01.example.com
|--@eu_west:
...

This is documented, not a bug. The tool says so in its own help:

Read-only / Safethe help text, verbatim
$ ansible-inventory --help
Actions:
One of following must be used on invocation, ONLY ONE!

--graph               create inventory graph, if supplying pattern it must
                      be a valid group name. It will ignore limit
--host HOST           Output specific host info, works as inventory script.
                      It will ignore limit
--list                Output all hosts info, works as inventory script

--list does honour it:

Read-only / Safethe same limit, on --list
$ ansible-inventory -i inventory/hosts.yml --list --limit canary --yaml
all:
children:
  canary:
    hosts:
      web01.example.com: {}
  eu_west:
    hosts:
      web01.example.com: {}
  production:
    children:
      web:
        hosts:
          web01.example.com:
            ansible_host: 192.0.2.11

The other half of scope: which tasks

Knowing the hosts answers half the question. --list-tasks answers the other half, and it resolves includes, imports and roles rather than showing you the file.

Read-only / Safeevery task this run would execute
$ ansible-playbook -i inventory/hosts.yml site.yml --list-tasks
playbook: site.yml

play #1 (web): Configure the web tier	TAGS: []
  tasks:
    Install the package	TAGS: [packages]
    Render the configuration	TAGS: [config]
    Restart the service	TAGS: [config, restart]

play #2 (db): Configure the database tier	TAGS: []
  tasks:
    Tune the database	TAGS: [config]

It honours --tags, which is how you verify that a tagged run does what you think:

Read-only / Safethe same playbook, filtered by tag
$ ansible-playbook -i inventory/hosts.yml site.yml --list-tasks --tags config
  play #1 (web): Configure the web tier	TAGS: []
  tasks:
    Render the configuration	TAGS: [config]
    Restart the service	TAGS: [config, restart]

play #2 (db): Configure the database tier	TAGS: []
  tasks:
    Tune the database	TAGS: [config]

--list-tags gives the vocabulary rather than the tasks — useful when somebody hands you an unfamiliar playbook and a ticket saying “run it with the config tag”:

Read-only / Safewhat tags exist
$ ansible-playbook -i inventory/hosts.yml site.yml --list-tags
  play #1 (web): Configure the web tier	TAGS: []
    TASK TAGS: [config, packages, restart]

play #2 (db): Configure the database tier	TAGS: []
    TASK TAGS: [config]

The four commands, and what each is for

QuestionCommandHonours --limit
What will this run touch?ansible-playbook <pb> --limit X --list-hostsYes — authoritative
What does this pattern resolve to?ansible <pattern> --list-hostsYes (-l also works)
What does my inventory contain?ansible-inventory --graphNo — ignored
What variables do the in-scope hosts have?ansible-inventory --list --limit XYes
What tasks would run?ansible-playbook <pb> --list-tasksHost list not shown
What tags exist?ansible-playbook <pb> --list-tagsn/a
  1. Decide the number first. "This should hit about twelve hosts" is the expectation, and it has to exist before the output does.
  2. Build the exact command you intend to run, then append --list-hosts.
  3. Read the count on each play. Compare it with the number from step one. A mismatch stops here.
  4. Spot-check the list itself for a host that should not be there, especially one from a different environment.
  5. Append --list-tasks instead, and confirm the task set matches the change you think you are making, including under any --tags.
  6. Remove the --list flag and run, with --check --diff first if the change is not trivially reversible.

Step one is the step people skip, and it is the one that makes the rest work. The output is not self-validating: twelve hosts and three hundred hosts look equally reasonable on screen. Only the comparison against a prior expectation turns the number into information.

Knowledge check

Knowledge check · 5 questions

  1. Q1. Which command answers "which hosts will this playbook change" most completely?

  2. Q2. You run ansible-inventory --graph --limit canary and see every host in the inventory. What has happened?

  3. Q3. --list-hosts shows only the hosts in the first serial batch when the play sets serial.

  4. Q4. A verification run and the real run selected different hosts. Which explanations point at a genuine problem worth investigating? Select all that apply.

  5. Q5. Why does the procedure insist on deciding the expected host count before running --list-hosts?

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