Ansible Inventory Files: INI, YAML, Groups, and Variables

Learn how Ansible inventory files list hosts and groups in INI or YAML, where to place variables, how parent and child groups work, and how to verify with ansible-inventory and ping on Rocky Linux 10.

Published

Updated

Read time 13 min read

Reviewed byDeepak Prasad

Ansible inventory file with host groups, variables, and ansible-inventory verification on Rocky Linux

An Ansible inventory file answers one question before every run: which hosts should Ansible touch, and what variables apply to them? Without inventory, ansible and ansible-playbook have no remote targets. Ansible can create an implicit localhost when you reference localhost, but for project work you should still define inventory clearly so host lists, groups, and variables are predictable.

This guide covers static INI and YAML inventory, groups and children, host and group variables, special connection variables, verification with ansible-inventory, and the layout used in the GoLinuxCloud course on Rocky Linux 10. You need Ansible installed on the control node and the lab SSH setup complete before you test against rocky2.

Tested on: Rocky Linux 10.2 (Red Quartz); kernel 6.12.0-211.16.1.el10_2.0.1.x86_64; ansible-core 2.16.16.

NOTE
This chapter is part of the GoLinuxCloud Ansible tutorial (RHCE EX294). Follow along from ~/ansible-project, inventory group lab, and playbooks in playbooks/. Use your own host names and paths if yours differ.

What is an Ansible Inventory File?

An inventory file (or inventory directory) lists managed hosts, organizes them into groups, and can attach variables Ansible merges before each task runs. Ansible reads inventory on the control node only—managed nodes never store a copy.

Inventory is not playbooks or roles. It is the host list ansible-playbook uses when a play declares hosts: lab or when you run ansible rocky2 -m ansible.builtin.ping.


Why Ansible Needs an Inventory

Every ansible and ansible-playbook run needs at least one target host. Inventory supplies:

  • Hostnames or aliases Ansible uses in output and logs
  • Groups so you can run against web, db, or lab instead of typing every name
  • Variables such as ansible_user, ansible_host, and ansible_become before play vars and facts

Point Ansible at inventory with -i, with inventory = in ansible.cfg, or with ansible.inventory.entries in ansible-navigator.yml. If the path is wrong, you get empty host lists or SSH to the wrong machine.


Static Inventory vs Dynamic Inventory

Type Source Typical use
Static INI or YAML files in your repo Labs, fixed fleets, learning, Git-reviewed host lists
Dynamic Inventory script or inventory plugin Cloud APIs, CMDB queries, lists that change every hour

Static inventory is what this guide focuses on—files you edit and commit. Dynamic inventory calls a script or plugin at runtime (for example amazon.aws.aws_ec2 or a custom script that prints JSON). The output shape matches static inventory: groups, hosts, and _meta.hostvars.

For a two-node lab, static inventory/hosts is enough. Reach for dynamic inventory when your host list lives in AWS, VMware, or another system of record.

This course stays with static inventory until that model is comfortable. When your fleet outgrows flat files—cloud autoscaling, CMDB exports, or another system of record—inventory plugins query that source at runtime and return the same host and group shape you already use here. That is a separate topic, not a full lesson in this chapter: list plugins with ansible-doc -t inventory -l, then read one with ansible-doc -t inventory PLUGIN_NAME (for example amazon.aws.aws_ec2) before you wire it into ansible.cfg.


Where to Create the Inventory File

Common layouts:

Location When to use
inventory/hosts in the project root Default for course labs—beside ansible.cfg
inventory/ directory Multiple files (web, db, group_vars/)
/etc/ansible/hosts System-wide fallback on the control node
-i path on the CLI One-off runs, CI, or switching environments

The project directory structure guide places inventory/hosts with inventory/group_vars/ and inventory/host_vars/ under inventory/ so ad hoc commands pick up variables without a playbook.

Recommended for this course:

text
inventory/
├── hosts
├── group_vars/
│   └── lab.yml
└── host_vars/
    └── rocky2.yml

Basic Ansible Inventory File Example

Minimal lab inventory—one group, one managed host, shared login user:

ini
[lab]
rocky2 ansible_host=192.168.56.109

[lab:vars]
ansible_user=ansible
ansible_become=true

In INI inventory, values defined inline on host lines and values defined in [group:vars] are parsed differently. For simple strings such as ansible_user this is fine, but for booleans, lists, or dictionaries, prefer YAML inventory or inventory/group_vars/*.yml.

Save as ~/ansible-project/inventory/hosts after you complete lab setup. Wire the path in ansible.cfg:

ini
[defaults]
inventory = inventory/hosts

Ansible Inventory File in INI Format

INI inventory uses section headers in square brackets. Host lines sit under a group; variables can sit on the host line or in [groupname:vars] blocks.

Add individual hosts

List one host per line. Ungrouped hosts land in the implicit ungrouped group:

ini
rocky2 ansible_host=192.168.56.109
rocky3 ansible_host=192.168.56.110

Create host groups

Square brackets define a group name. Hosts under [lab] are members of lab:

ini
[lab]
rocky2 ansible_host=192.168.56.109
rocky3 ansible_host=192.168.56.110

[monitoring]
mon1 ansible_host=192.168.56.120

Run against one group:

bash
cd ~/ansible-project
ansible lab --list-hosts

Sample output:

output
hosts (2):
    rocky2
    rocky3

Add host ranges

When many hosts follow the same naming pattern, use a range instead of listing every host:

ini
[web]
web[01:05].example.com

This expands to:

Sample output:

output
web01.example.com
web02.example.com
web03.example.com
web04.example.com
web05.example.com

You can also use a stride:

ini
[web]
web[01:09:2].example.com

This matches web01, web03, web05, web07, and web09.

Alphabetic ranges also work:

ini
[db]
db-[a:c].example.com

Host ranges are useful when DNS names are predictable. For small labs like rocky2, list hosts explicitly so beginners can see the exact IP mapping.

Add host aliases

The first token on the line is the inventory hostname Ansible prints in recap output. ansible_host sets the address SSH uses when it differs:

ini
[lab]
rocky2 ansible_host=192.168.56.109
db-primary ansible_host=192.168.56.111 ansible_port=22

Here db-primary is the inventory name; Ansible connects to 192.168.56.111.

Add host variables

Attach variables on the host line with key=value pairs:

ini
[lab]
rocky2 ansible_host=192.168.56.109 ansible_user=ansible web_port=8080

Host-line variables override group variables for the same key on that host only.

Add group variables

Use [groupname:vars] for settings every host in the group shares:

ini
[lab]
rocky2 ansible_host=192.168.56.109
rocky3 ansible_host=192.168.56.110

[lab:vars]
ansible_user=ansible
ansible_become=true
web_package=httpd

Do not put per-host IPs in [lab:vars] when each machine has a different address—use ansible_host on each host line or inventory/host_vars/.

Create child groups with :children

Parent groups combine existing groups. Use the :children suffix:

ini
[web]
web1 ansible_host=192.168.56.110
web2 ansible_host=192.168.56.111

[db]
db1 ansible_host=192.168.56.112

[production:children]
web
db

production includes every host in web and db. Target the parent group in plays or ad hoc commands:

bash
ansible production --list-hosts

Ansible Inventory File in YAML Format

YAML inventory describes the same data as INI with nested keys. Ansible accepts .yml or .yaml files when you pass them with -i or list them in an inventory directory.

Add individual hosts

Top-level keys under hosts define members of all:

yaml
all:
  hosts:
    rocky2:
      ansible_host: 192.168.56.109
    rocky3:
      ansible_host: 192.168.56.110

Create host groups

Use children to define groups under all:

yaml
all:
  children:
    lab:
      hosts:
        rocky2:
          ansible_host: 192.168.56.109
        rocky3:
          ansible_host: 192.168.56.110

Add host aliases

The YAML key is the inventory hostname; ansible_host carries the connect address:

yaml
all:
  children:
    lab:
      hosts:
        db-primary:
          ansible_host: 192.168.56.111

Add host variables

Nest variables under the host key:

yaml
all:
  children:
    lab:
      hosts:
        rocky2:
          ansible_host: 192.168.56.109
          web_port: 8080

Add group variables

Use a vars key at the group level:

yaml
all:
  children:
    lab:
      hosts:
        rocky2:
          ansible_host: 192.168.56.109
      vars:
        ansible_user: ansible
        ansible_become: true
        web_package: httpd

Create child groups with children

Nest groups under a parent children block:

yaml
all:
  children:
    production:
      children:
        web:
          hosts:
            web1:
              ansible_host: 192.168.56.110
        db:
          hosts:
            db1:
              ansible_host: 192.168.56.112

production contains all hosts from web and db.


INI vs YAML Inventory Format

INI YAML
Readability in small labs Familiar bracket syntax More indentation
Nested groups :children sections Native children trees
Inline host vars host var=value on one line Key/value under host
Common in RHCE-style briefs, quick edits Large dynamic-looking static files
Mixing in one project One format per file Do not mix INI and YAML in the same file

Ansible does not prefer one format for correctness—pick what your team edits reliably. Many repos keep INI for inventory/hosts and YAML for group_vars/*.yml.


Ansible Inventory Groups Explained

Groups are labels for hosts. You reference them in playbooks:

yaml
- name: Patch lab systems
  hosts: lab
  tasks:
    - ansible.builtin.dnf:
        name: '*'
        state: latest

Every inventory host belongs to the all group. Hosts that are not placed in any explicit group also appear under ungrouped. Use simple group names such as web, db, lab, and production. Avoid spaces, hyphens, and dots unless you understand how they affect host patterns and variable access.


Parent and Child Groups in Ansible Inventory

Child groups inherit membership upward. If web and db are children of production, then hosts: production hits every host in both child groups.

INI:

ini
[web]
web1 ansible_host=192.168.56.110

[db]
db1 ansible_host=192.168.56.112

[production:children]
web
db

Verify the tree:

bash
cd ~/ansible-project
ansible-inventory --graph

Sample output:

output
@all:
  |--@ungrouped:
  |--@production:
  |  |--@web:
  |  |  |--web1
  |  |--@db:
  |  |  |--db1

To see group hierarchy and attached variables together:

bash
ansible-inventory --graph --vars

Sample output:

output
@all:
  |--@ungrouped:
  |--@production:
  |  |--@web:
  |  |  |--web1
  |  |  |  |--{ansible_host = 192.168.56.110}
  |  |  |  |--{ansible_user = ansible}
  |  |--@db:
  |  |  |--db1
  |  |  |  |--{ansible_host = 192.168.56.112}

Variables on the parent group apply to all descendant hosts unless a child group or host overrides them.


Inventory Patterns and --limit

Inventory defines hosts and groups, but patterns decide which subset Ansible runs against. You use a pattern in ad hoc commands:

bash
ansible lab -m ansible.builtin.ping

and in playbooks:

yaml
- name: Run on lab hosts
  hosts: lab
  tasks:
    - ansible.builtin.ping:

Common patterns:

Pattern Meaning
all All inventory hosts
lab All hosts in the lab group
rocky2 One inventory host
web:db Hosts in web or db
web:!db Hosts in web except hosts also in db
web:&staging Hosts that are in both web and staging
*.example.com Hosts matching a wildcard pattern

Use --limit to narrow a playbook run without editing inventory:

bash
ansible-playbook site.yml --limit rocky2

or:

bash
ansible-playbook site.yml --limit 'web:!db'

Use quotes around patterns with !, &, or wildcards so the shell does not interpret them.

Use ansible --list-hosts PATTERN before a risky command to confirm which hosts the pattern expands to:

bash
ansible 'web:!db' --list-hosts

Host Aliases in Ansible Inventory

Ansible distinguishes:

Term Meaning
Inventory hostname Name in inventory and recap (rocky2, db-primary)
ansible_host IP or DNS Ansible connects to
ansible_port SSH port when not 22

Use a short inventory name when DNS is ugly but keep ansible_host accurate for your lab network:

ini
[lab]
r2 ansible_host=192.168.56.109

Plays and ad hoc commands use r2; SSH goes to 192.168.56.109.


Inventory Variables Explained

Variables from inventory feed templates, when conditions, and module arguments. Ansible merges them with playbook vars, role defaults, and facts—see variable precedence for the full stack.

Basic inventory variable precedence

For inventory-level variables, think from broad to specific:

Priority Source Notes
Lowest all vars Applies to every host
Higher Parent group vars Applies through child groups
Higher Child group vars Overrides parent group vars
Highest Host vars Overrides group vars for that host

Example: if web_package is set in both inventory/group_vars/lab.yml and inventory/host_vars/rocky2.yml, the host-specific value from rocky2.yml wins for rocky2.

Host variables

Apply to one inventory hostname—on the host line, in YAML under the host, or in inventory/host_vars/hostname.yml:

yaml
# inventory/host_vars/rocky2.yml
ansible_host: 192.168.56.109
web_port: 8080

Group variables

Apply to every host in the group—[group:vars] in INI, vars: in YAML, or inventory/group_vars/groupname.yml:

yaml
# inventory/group_vars/lab.yml
ansible_user: ansible
web_package: httpd

all group variables

Settings for every host go in [all:vars] (INI) or under all.vars (YAML):

ini
[all:vars]
ansible_python_interpreter=/usr/bin/python3

Use sparingly—over-broad all vars hide which group actually owns a setting.

Variables inside inventory vs group_vars and host_vars

You can define variables inline in inventory/hosts or in separate files. The host_group_vars vars plugin loads group_vars/ and host_vars/ relative to the inventory source or playbook.

For ad hoc commands there is no playbook, so keep inventory/group_vars/ and inventory/host_vars/ next to inventory/hosts—the layout described in the project directory structure guide.

Style Good for
Inline in hosts Small labs, connection vars (ansible_host, ansible_user)
inventory/group_vars/*.yml Shared app settings, encrypted vault files
inventory/host_vars/*.yml Per-host IPs, ports, rack labels

Special Inventory Variables

These connection variables are common in inventory and override ansible.cfg defaults:

Variable Purpose
ansible_host Target IP or hostname for SSH
ansible_user Remote login user
ansible_port SSH port (default 22)
ansible_connection Plugin name (ssh, local, winrm, …)
ansible_become Whether to escalate privileges
ansible_become_user User after become (often root)
ansible_python_interpreter Python path on the target
ansible_ssh_private_key_file Key path when not default

Modern Ansible uses ansible_user instead of legacy ansible_ssh_user. Prefer the non-ssh_ names in new files.


Verify Inventory with ansible-inventory

From the project root, list merged hosts and variables:

bash
cd ~/ansible-project
ansible-inventory --list

Sample output (abbreviated):

output
{
    "_meta": {
        "hostvars": {
            "rocky2": {
                "ansible_become": "true",
                "ansible_host": "192.168.56.109",
                "ansible_user": "ansible"
            }
        }
    },
    "all": {
        "children": [
            "ungrouped",
            "lab"
        ]
    },
    "lab": {
        "hosts": [
            "rocky2"
        ]
    }
}

YAML-shaped output is easier to read for large inventories:

bash
ansible-inventory --list --yaml | head -15

Sample output:

output
all:
  children:
    lab:
      hosts:
        rocky2:
          ansible_become: 'true'
          ansible_host: 192.168.56.109
          ansible_user: ansible

Inspect one host:

bash
ansible-inventory --host rocky2

Sample output:

output
{
    "ansible_become": "true",
    "ansible_host": "192.168.56.109",
    "ansible_user": "ansible"
}

Test Inventory with Ansible Ping

ansible-inventory proves parsing; ping proves SSH and Python on the target.

bash
cd ~/ansible-project
ansible rocky2 -m ansible.builtin.ping

Sample output:

output
rocky2 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

Ping an entire group:

bash
ansible lab -m ansible.builtin.ping

If ping fails, fix SSH and sudo from lab setup before you debug inventory syntax.


Use Inventory from ansible.cfg

Set the default path once in ansible.cfg:

ini
[defaults]
inventory = inventory/hosts

Confirm Ansible picked up the file:

bash
cd ~/ansible-project
ansible --version

Sample output:

output
ansible [core 2.16.16]
  config file = /home/ansible/ansible-project/ansible.cfg
  ...

Override for one run:

bash
ansible -i inventory/staging/hosts rocky2 -m ansible.builtin.ping

Navigator can list the same inventory when entries are set in ansible-navigator.yml.


Multiple Inventory Files and Directories

Point inventory at a directory when you want Ansible to read multiple inventory sources from that directory. Keep variable files under group_vars/ and host_vars/; those are loaded as variable directories, not as inventory source files.

ini
[defaults]
inventory = inventory/

Example tree—multiple inventory source files in one directory:

text
inventory/
├── 01-hosts.ini
├── 02-cloud.yml
├── group_vars/
│   └── lab.yml
└── host_vars/
    └── rocky2.yml

Ansible reads 01-hosts.ini and 02-cloud.yml as inventory sources. group_vars/ and host_vars/ supply variables only—they are not parsed as host lists.

Pass several sources explicitly when you prefer separate paths over a directory:

bash
ansible -i inventory/hosts -i inventory/cloud.yml web -m ansible.builtin.ping

When using an inventory directory, keep source files intentionally named and avoid defining the same host or variable in many places. If the same variable is defined more than once, the later loaded value can overwrite the earlier one. Use ansible-inventory --list to confirm the final merged result.

Later entries can override earlier ones for the same host when you pass multiple -i sources—use deliberate naming when you split environments.


Common Ansible Inventory Mistakes and Fixes

Mistake Why it hurts Fix
ansible_host in [group:vars] Every host gets the same IP Per-host line or inventory/host_vars/
group_vars/lab.yml but group is [web] Variables not loaded Filename must match group name; use inventory/group_vars/
Wrong -i or wrong cwd Empty host list or wrong config cd to project root; check ansible --version config line
Typo in group name Play skips hosts ansible-inventory --graph
Legacy ansible_ssh_pass in Git Plaintext password in repo SSH keys from lab setup; vault for secrets
Mixing NAT and host-only IP SSH to wrong interface Use the host-only address from the lab topology table

On rocky1, use ~/ansible-project with:

text
inventory/
├── hosts
├── group_vars/
│   └── lab.yml
└── host_vars/
    └── rocky2.yml

Starter inventory/hosts:

ini
[lab]
rocky2 ansible_host=192.168.56.109

[lab:vars]
ansible_user=ansible
ansible_become=true

After inventory works, add playbooks using playbook structure and keep variables in inventory/group_vars/ as labs grow. Architecture context: what is Ansible.


What comes next

  1. group_vars, host_vars and inventory patterns — variable placement and targeting
  2. Ansible modules and ansible-doc — FQCN and module discovery
  3. Ansible playbook examples — multi-task workflows

References


Summary

An Ansible inventory file lists managed hosts, organizes them into groups and parent/child trees, and supplies variables Ansible merges before each run. Use INI or YAML static files in inventory/, keep inventory/group_vars/ and inventory/host_vars/ beside the inventory source, set inventory = in ansible.cfg, and verify with ansible-inventory --list and ansible HOST -m ansible.builtin.ping. For the course lab, one [lab] group with rocky2 and shared ansible_user is enough to start.


Frequently Asked Questions

1. What is the default Ansible inventory file?

On many Linux installs, /etc/ansible/hosts is the system fallback. For projects, point ansible.cfg at inventory/hosts or an inventory directory under your project root and cd there before you run ansible.

2. Should I use INI or YAML for Ansible inventory?

INI is common in labs and short examples. YAML scales better for nested groups and complex host vars. Ansible accepts both; pick one format per file and stay consistent in the repo.

3. Where should group_vars and host_vars live?

Place them under inventory/ as inventory/group_vars/ and inventory/host_vars/ so ad hoc commands resolve variables without a playbook. File names must match group or host names.

4. What is the difference between ansible_host and inventory_hostname?

inventory_hostname is the name Ansible uses from inventory—rocky2 in our lab. ansible_host is the connection address when it differs, such as 192.168.56.109 on a host-only network.

5. How do I test inventory before running playbooks?

Run ansible-inventory --list or --graph from the project root, then ansible HOST -m ansible.builtin.ping for a live SSH check.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive …