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.
~/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, orlabinstead of typing every name - Variables such as
ansible_user,ansible_host, andansible_becomebefore 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:
inventory/
├── hosts
├── group_vars/
│ └── lab.yml
└── host_vars/
└── rocky2.ymlBasic Ansible Inventory File Example
Minimal lab inventory—one group, one managed host, shared login user:
[lab]
rocky2 ansible_host=192.168.56.109
[lab:vars]
ansible_user=ansible
ansible_become=trueIn 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:
[defaults]
inventory = inventory/hostsAnsible 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:
rocky2 ansible_host=192.168.56.109
rocky3 ansible_host=192.168.56.110Create host groups
Square brackets define a group name. Hosts under [lab] are members of lab:
[lab]
rocky2 ansible_host=192.168.56.109
rocky3 ansible_host=192.168.56.110
[monitoring]
mon1 ansible_host=192.168.56.120Run against one group:
cd ~/ansible-project
ansible lab --list-hostsSample output:
hosts (2):
rocky2
rocky3Add host ranges
When many hosts follow the same naming pattern, use a range instead of listing every host:
[web]
web[01:05].example.comThis expands to:
Sample output:
web01.example.com
web02.example.com
web03.example.com
web04.example.com
web05.example.comYou can also use a stride:
[web]
web[01:09:2].example.comThis matches web01, web03, web05, web07, and web09.
Alphabetic ranges also work:
[db]
db-[a:c].example.comHost 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:
[lab]
rocky2 ansible_host=192.168.56.109
db-primary ansible_host=192.168.56.111 ansible_port=22Here 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:
[lab]
rocky2 ansible_host=192.168.56.109 ansible_user=ansible web_port=8080Host-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:
[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=httpdDo 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:
[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
dbproduction includes every host in web and db. Target the parent group in plays or ad hoc commands:
ansible production --list-hostsAnsible 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:
all:
hosts:
rocky2:
ansible_host: 192.168.56.109
rocky3:
ansible_host: 192.168.56.110Create host groups
Use children to define groups under all:
all:
children:
lab:
hosts:
rocky2:
ansible_host: 192.168.56.109
rocky3:
ansible_host: 192.168.56.110Add host aliases
The YAML key is the inventory hostname; ansible_host carries the connect address:
all:
children:
lab:
hosts:
db-primary:
ansible_host: 192.168.56.111Add host variables
Nest variables under the host key:
all:
children:
lab:
hosts:
rocky2:
ansible_host: 192.168.56.109
web_port: 8080Add group variables
Use a vars key at the group level:
all:
children:
lab:
hosts:
rocky2:
ansible_host: 192.168.56.109
vars:
ansible_user: ansible
ansible_become: true
web_package: httpdCreate child groups with children
Nest groups under a parent children block:
all:
children:
production:
children:
web:
hosts:
web1:
ansible_host: 192.168.56.110
db:
hosts:
db1:
ansible_host: 192.168.56.112production 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:
- name: Patch lab systems
hosts: lab
tasks:
- ansible.builtin.dnf:
name: '*'
state: latestEvery 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:
[web]
web1 ansible_host=192.168.56.110
[db]
db1 ansible_host=192.168.56.112
[production:children]
web
dbVerify the tree:
cd ~/ansible-project
ansible-inventory --graphSample output:
@all:
|--@ungrouped:
|--@production:
| |--@web:
| | |--web1
| |--@db:
| | |--db1To see group hierarchy and attached variables together:
ansible-inventory --graph --varsSample 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:
ansible lab -m ansible.builtin.pingand in playbooks:
- 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:
ansible-playbook site.yml --limit rocky2or:
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:
ansible 'web:!db' --list-hostsHost 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:
[lab]
r2 ansible_host=192.168.56.109Plays 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:
# inventory/host_vars/rocky2.yml
ansible_host: 192.168.56.109
web_port: 8080Group variables
Apply to every host in the group—[group:vars] in INI, vars: in YAML, or inventory/group_vars/groupname.yml:
# inventory/group_vars/lab.yml
ansible_user: ansible
web_package: httpdall group variables
Settings for every host go in [all:vars] (INI) or under all.vars (YAML):
[all:vars]
ansible_python_interpreter=/usr/bin/python3Use 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:
cd ~/ansible-project
ansible-inventory --listSample output (abbreviated):
{
"_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:
ansible-inventory --list --yaml | head -15Sample output:
all:
children:
lab:
hosts:
rocky2:
ansible_become: 'true'
ansible_host: 192.168.56.109
ansible_user: ansibleInspect one host:
ansible-inventory --host rocky2Sample 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.
cd ~/ansible-project
ansible rocky2 -m ansible.builtin.pingSample output:
rocky2 | SUCCESS => {
"changed": false,
"ping": "pong"
}Ping an entire group:
ansible lab -m ansible.builtin.pingIf 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:
[defaults]
inventory = inventory/hostsConfirm Ansible picked up the file:
cd ~/ansible-project
ansible --versionSample output:
ansible [core 2.16.16]
config file = /home/ansible/ansible-project/ansible.cfg
...Override for one run:
ansible -i inventory/staging/hosts rocky2 -m ansible.builtin.pingNavigator 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.
[defaults]
inventory = inventory/Example tree—multiple inventory source files in one directory:
inventory/
├── 01-hosts.ini
├── 02-cloud.yml
├── group_vars/
│ └── lab.yml
└── host_vars/
└── rocky2.ymlAnsible 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:
ansible -i inventory/hosts -i inventory/cloud.yml web -m ansible.builtin.pingWhen 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 |
Recommended Inventory Structure for This Course
On rocky1, use ~/ansible-project with:
inventory/
├── hosts
├── group_vars/
│ └── lab.yml
└── host_vars/
└── rocky2.ymlStarter inventory/hosts:
[lab]
rocky2 ansible_host=192.168.56.109
[lab:vars]
ansible_user=ansible
ansible_become=trueAfter 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
- group_vars, host_vars and inventory patterns — variable placement and targeting
- Ansible modules and ansible-doc — FQCN and module discovery
- Ansible playbook examples — multi-task workflows
References
- Ansible inventory guide (upstream)
- How to build your inventory
- Inventory basics: formats, hosts, and groups
- Connecting to hosts: behavioral inventory parameters
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.

