CertGrid CertGrid
Hands-on Lab·Ansible

Ansible Static Inventory and Host Patterns

An inventory is a list of hosts arranged into groups, and the groups only earn their keep once you start selecting with them. This builds one in INI format with two groups, a parent group and group variables, works through the host patterns the exam expects, shows the failure mode of a pattern that matches nothing, and then writes the same inventory in YAML.

Control Node and Managed Nodes Guide 3 of 45 Beginner

Written against the versions above. Both INI and YAML inventories are fully supported and neither is deprecated. INI is faster to type under exam pressure; YAML is easier to keep correct once there is real nesting. `ansible-inventory --graph` renders either one the same way, which makes it the fastest way to check you wrote what you meant.

Two managed nodes in two groups. One group would make every pattern in this guide return the same answer.
Server NameIP AddressOSRolesCPURAMHDD
ANS-CTL01192.168.0.36Ubuntu 26.04 LTSAnsible Control Node2 Core3 GB50 GB
ANS-A01192.168.0.37Ubuntu 26.04 LTSManaged Node (group: web)2 Core3 GB50 GB
ANS-B01192.168.0.38Ubuntu 26.04 LTSManaged Node (group: db)2 Core3 GB50 GB

Before you start

  1. An inventory with two groups and a parent

    [web]
    ans-a01
    
    [db]
    ans-b01
    
    [production:children]
    web
    db
    
    [web:vars]
    app_port=8080

    Four ideas in nine lines: groups, a parent group built from other groups with :children, group variables with :vars, and [all:vars] for everything.

    @all:
      |--@ungrouped:
      |--@web:
      |  |--ans-a01
      |--@db:
      |  |--ans-b01

    ansible-inventory --graph is the check to run every time you edit an inventory. Two groups exist implicitly and always: all, and ungrouped for hosts you listed outside any group.

    Add --vars and the graph shows which variables a group carries, which is how you confirm a :vars block landed where you intended.

    bash Example session
    cat > ~/inventory.ini <<'EOF'[web]ans-a01 [db]ans-b01 [production:children]webdb [web:vars]app_port=8080 [all:vars]ansible_python_interpreter=/usr/bin/python3EOFcat ~/inventory.ini[web]ans-a01 [db]ans-b01 [production:children]webdb [web:vars]app_port=8080 [all:vars]ansible_python_interpreter=/usr/bin/python3ansible-inventory --graph@all:  |--@ungrouped:  |--@production:  |  |--@web:  |  |  |--ans-a01  |  |--@db:  |  |  |--ans-b01ansible-inventory --graph --vars web@web:  |--ans-a01  |  |--{ansible_python_interpreter = /usr/bin/python3}  |  |--{app_port = 8080}  |--{app_port = 8080}

    Expected resultA graph showing web, db, and their parent.

    Success conditionYou can write an inventory and verify it in one command.

  2. Patterns decide who runs

    Every ansible and ansible-playbook invocation takes a pattern, and --list-hosts answers 'who would this hit' without running anything:

    | Pattern | Meaning | |---|---| | all | every host | | web | one group | | production | a parent group, expanded | | web:db | the union - a colon is OR, not AND | | all:!db | everything except a group | | ans-* | glob on the host name |

    The one that catches people is web:db. It reads like an intersection and it is a union; the intersection operator is :&.

    --list-hosts before anything destructive is a cheap habit. It is the difference between restarting a service on one group and restarting it on the fleet.

    bash Example session
    ansible all --list-hosts  hosts (2):    ans-a01    ans-b01ansible web --list-hosts  hosts (1):    ans-a01ansible production --list-hosts  hosts (2):    ans-a01    ans-b01ansible 'web:db' --list-hosts  hosts (2):    ans-a01    ans-b01ansible 'all:!db' --list-hosts  hosts (1):    ans-a01ansible 'ans-*' --list-hosts  hosts (2):    ans-a01    ans-b01

    Expected resultSix patterns and the hosts each resolves to.

    Success conditionYou can target exactly the hosts you mean.

  3. A pattern that matches nothing

    Ask for a group that does not exist:

    [WARNING]: Could not match supplied host pattern, ignoring: cache
    hosts (0):

    and running a module against it:

    [WARNING]: No hosts matched, nothing to do

    A warning, not an error, and an exit code of 0. A typo in a group name does not fail your command - it succeeds against nobody. In a pipeline or a script that is a silent no-op reported as success, and it is one of the more expensive small mistakes in Ansible.

    Guard against it the same way: --list-hosts first, and read the count.

    bash Example session
    ansible 'cache' --list-hosts[WARNING]: Could not match supplied host pattern, ignoring: cache[WARNING]: No hosts matched, nothing to do  hosts (0):ansible 'cache' -m ping[WARNING]: Could not match supplied host pattern, ignoring: cache[WARNING]: No hosts matched, nothing to do

    Expected resultA warning, zero hosts, and a successful exit.

    Success conditionYou will not trust a play that quietly matched nothing.

  4. The same inventory in YAML

    The nesting is explicit, which is the point:

    all:
      children:
        production:
          children:
            web:
              hosts:
                ans-a01:
              vars:
                app_port: 8080

    --graph renders it identically to the INI version, so the two really are interchangeable. --host dumps everything one host has resolved, merging group variables from every group it belongs to - which is the command to reach for when a variable is not what you expected.

    And -i overrides the inventory from ansible.cfg for a single run, which is how you test a new inventory without editing your config:

    ansible -i ~/inventory.yml production -m ping

    Which format to use: INI when you are typing against a clock, YAML when the structure has real depth or when the same file carries a lot of variables. Both are examinable; neither is going away.

    bash Example session
    cat > ~/inventory.yml <<'EOF'all:  vars:    ansible_python_interpreter: /usr/bin/python3  children:    production:      children:        web:          hosts:            ans-a01:          vars:            app_port: 8080        db:          hosts:            ans-b01:EOFansible-inventory -i ~/inventory.yml --graph@all:  |--@ungrouped:  |--@production:  |  |--@web:  |  |  |--ans-a01  |  |--@db:  |  |  |--ans-b01ansible-inventory -i ~/inventory.yml --host ans-a01{    "ansible_python_interpreter": "/usr/bin/python3",    "app_port": 8080}ansible -i ~/inventory.yml production -m pingans-a01 | SUCCESS => {    "changed": false,    "ping": "pong"}ans-b01 | SUCCESS => {    "changed": false,    "ping": "pong"}

    Expected resultAn identical graph from a different file format.

    Success conditionYou can write an inventory in either format and check it the same way.

Troubleshooting

Official sources