What is Inventory? #

In every infrastructure automation initiative, clarity about execution targets is the most crucial thing. Before you can install application packages, update system configuration, or trigger a deployment process, you must answer one fundamental question: Which servers should these instructions be sent to? In the Ansible ecosystem, the answer to that question is centrally managed by a component called the Inventory. This article covers the basic concept of inventory as the single source of truth for your infrastructure, compares the supported file formats, breaks down built-in group structures and hierarchical grouping patterns, and explains how Ansible reads and validates the inventory.


The Source of Truth for Infrastructure #

In Ansible, the inventory isn’t just a static list of IP addresses typed into a file. The inventory is a declarative abstraction of every machine, virtual server, network device, or container you manage. It acts as the Source of Truth that separates automation action logic (what the playbook does) from target data (who receives the action).

Systematically, the inventory file defines four main dimensions of your infrastructure:

  1. Node Identity (Who): The hostname, domain name (FQDN), or IP address of each target managed node.
  2. Logical Grouping (How to group): Server classification by function (for example the webservers, dbservers groups), by environment (development, production), or by geographic region (jakarta, surabaya).
  3. Variable Contextualization (What variables): Specific configuration data bound to a particular server group or host (for example HTTP port, log directory path, or database name).
  4. Connection Method (How to connect): The tactical parameters Ansible uses to initiate SSH connections, such as login user (ansible_user), custom SSH port (ansible_port), and private key file location (ansible_ssh_private_key_file).

When you run a playbook, Ansible processes the inventory file first to map which servers fall into the execution scope, assembles the variable data applicable to each host, and only then initiates parallel connections to run the automation modules.


INI and YAML Formats #

Ansible supports two formats for writing static inventory files: INI and YAML. Both formats produce the exact same internal data structure in Ansible’s memory. The format choice entirely depends on infrastructure complexity and your development team’s comfort.

1. INI Format (Traditional Standard) #

The INI format is Ansible’s original native format and the most popular because its structure is very compact, simple, and quick to read. It’s well-suited for small to mid-sized inventories written and maintained manually by system administrators.

Here’s an example INI format inventory file:

# File: inventory/production/hosts.ini

# Server without a group (automatically goes into the 'ungrouped' group)
bastion.unisbadri.com

# Server grouping by function
[webservers]
web-01.unisbadri.com http_port=80
web-02.unisbadri.com http_port=8080
192.168.56.12 http_port=80

[dbservers]
db-primary.unisbadri.com
db-replica-01.unisbadri.com

# Group that combines other groups (Hierarchy)
[datacenter:children]
webservers
dbservers

# Defining variables that apply to all hosts in a specific group
[webservers:vars]
ansible_user=ubuntu
nginx_version=1.24

2. YAML Format (Modern Standard) #

The YAML format is more explicit, has a strict indentation-based structure, and is well-suited if you frequently generate inventory files programmatically using external scripts or CI/CD integration systems. Its advantage is syntax consistency with Ansible playbook files, which are also written in YAML.

Here’s the same inventory file written in YAML format:

# File: inventory/production/hosts.yml
---
all:
  hosts:
    bastion.unisbadri.com: {}
  children:
    webservers:
      hosts:
        web-01.unisbadri.com:
          http_port: 80
        web-02.unisbadri.com:
          http_port: 8080
        192.168.56.12:
          http_port: 80
      vars:
        ansible_user: ubuntu
        nginx_version: "1.24"
    dbservers:
      hosts:
        db-primary.unisbadri.com: {}
        db-replica-01.unisbadri.com: {}
    datacenter:
      children:
        webservers: {}
        dbservers: {}

3. Writing Host Ranges #

When you have to define dozens to hundreds of servers with sequential name patterns (for example web-01.example.com through web-50.example.com), you don’t need to write them one by one. Ansible supports numerical range and alphabetic range syntax.

  • Numerical Ranges (INI Format):
    [webservers]
    # Defines web-01.example.com through web-50.example.com
    web-[01:50].example.com
    
    # Defines IPs 192.168.1.10 through 192.168.1.25
    192.168.1.[10:25]
    
  • Numerical Ranges (YAML Format):
    webservers:
      hosts:
        # Numerical ranges in YAML still need quotes
        "web-[01:50].example.com": {}
        "192.168.1.[10:25]": {}
    
  • Alphabetic Ranges (INI Format):
    [dbservers]
    # Defines db-a.example.com through db-f.example.com
    db-[a:f].example.com
    

This range feature drastically shrinks the inventory file size and reduces the risk of typos when adding a new series of homogeneous servers.


Built-in Group Structure and Hierarchy #

Every time you load an inventory, Ansible automatically creates two implicit groups you never need to declare explicitly in the file:

  • all: The root group that includes every host defined across the entire inventory without exception.
  • ungrouped: The container group for every host defined outside any group block (like the bastion.unisbadri.com host in the example above).

The tree structure visualization of these built-in and custom groups is shown in the diagram below:

flowchart TD
    A["all group (Built-in)"] --> B["ungrouped group (Built-in)"]
    A --> C["datacenter group (Custom)"]
    B --> D["bastion.unisbadri.com"]
    C --> E["webservers group (Child)"]
    C --> F["dbservers group (Child)"]
    E --> G["web-01.unisbadri.com"]
    E --> H["web-02.unisbadri.com"]
    E --> I["192.168.56.12"]
    F --> J["db-primary.unisbadri.com"]
    F --> K["db-replica-01.unisbadri.com"]

Group Inheritance and Shared Variables #

Building hierarchical group structures using the :children parameter (INI) or children: (YAML) lets you create elegant property inheritance relationships. For example, the parent datacenter group above combines the webservers and dbservers groups.

If you define variables at the datacenter group level, every server under the child groups (webservers and dbservers) automatically inherits those variables:

[datacenter:children]
webservers
dbservers

[datacenter:vars]
# This variable is inherited by web-01, web-02, db-primary, and db-replica
ntp_server=time.unisbadri.com
dns_resolver=1.1.1.1

Flexible Targeting Using Patterns and Limits #

With the grouping structure above, you can direct playbook execution very flexibly using the hosts: parameter in a playbook or the limit flag (--limit) on the CLI command line.

The table below explains the matching patterns you can use to filter target servers:

Pattern SyntaxTarget RecipientsLogic Explanation
allAll hostsRuns the task on every server in the inventory.
webserversOnly the webservers groupRuns the task on all hosts in that group.
webservers[0]First server in the groupTakes the first index (in this example web-01).
webservers[1:3]Server index rangeTakes servers at index 1 through 3 within the group.
webservers:dbserversUnionAll servers in the webservers group OR dbservers.
webservers:&dbserversIntersectionOnly servers in the webservers group AND dbservers.
webservers:!dbserversExclusionServers in the webservers group BUT NOT members of dbservers.
web*:db** wildcard charactersAll groups starting with the word web or db.

Example operational usage in the terminal:

# Only run the playbook on database replica servers
ansible-playbook -i inventory/hosts.ini playbooks/upgrade.yml --limit "dbservers_replica"

# Run the playbook on all webservers EXCEPT those behind the CDN
ansible-playbook -i inventory/hosts.ini playbooks/deploy.yml --limit "webservers:!webservers_cdn"

# Run an ad-hoc ping only on the intersection of web servers located in the Jakarta region
ansible -i inventory/hosts.ini "webservers:&jakarta" -m ping

Shared Inventory Directories #

When managing enterprise-level infrastructure, a single inventory file often becomes too large and hard to manage because different teams have responsibility for different servers (for example the security team manages monitoring servers, the database team manages database clusters).

To solve this, you can configure the inventory parameter in the ansible.cfg file to point to a directory instead of a single file.

# File: ansible.cfg
[defaults]
inventory = inventory/production/

Inside that inventory/production/ directory, you can split the inventory into several files by ownership or server type:

inventory/production/
  ├── 01-infrastructure.ini   # Bastion and core network file
  ├── 02-web-tier.yml         # Web application server inventory
  ├── 03-database-tier.ini    # Database cluster inventory
  └── group_vars/             # Unified group variables
      ├── all.yml
      └── webservers.yml

Ansible’s Directory Merging Rules: #

  1. Alphabetical Order: Ansible reads all files in the directory in alphabetical name order.
  2. Ignore Backup Extensions: Files ending with common backup extensions (like ~, .bak, .orig, .retry, or .old) are automatically ignored by Ansible to prevent loading duplicate or stale servers.
  3. Group Merging: If the same group name is defined in both 02-web-tier.yml and 03-database-tier.ini, Ansible intelligently merges the host lists from both files into one unified in-memory group.

The Reading Algorithm #

To make debugging easier when a variable has an unexpected value, it’s important to understand the order of Ansible’s algorithm when reading and assembling all inventory files from disk into runtime memory.

Ansible executes the loading steps in the following order:

flowchart TD
    Step1["1. Look for the inventory file at the specified path (-i argument or ansible.cfg)"]
    Step2["2. Parse the physical file (INI/YAML) to build host and group relationships"]
    Step3["3. Parse the group_vars/ subdirectory adjacent to the inventory file:<br/>- Read group_vars/all.yml (Load global variables)<br/>- Read group_vars/&lt;group_name&gt;.yml (Load specific group variables)"]
    Step4["4. Parse the host_vars/ subdirectory adjacent to the inventory file:<br/>- Read host_vars/&lt;host_name&gt;.yml (Load host-specific variables)"]
    Step5["5. Perform variable inheritance resolution (Precedence resolving):<br/>- host_vars variables override group_vars, group_vars override group_vars/all"]
    Step6["6. Assemble the final hostvars data structure for the Playbook Engine to execute"]

    Step1 --> Step2
    Step2 --> Step3
    Step3 --> Step4
    Step4 --> Step5
    Step5 --> Step6

This mechanism ensures the most specific variable data (like host-level variables) always has higher priority than general variable data (like group-level variables).


Inspecting and Tracing the Inventory #

Never run a playbook directly if you’re not sure Ansible has read your group and server configuration correctly. Ansible provides a dedicated built-in tracing tool called ansible-inventory.

Here are the essential diagnostic commands you must master:

1. Displaying the Host and Group Relationship Graph #

This command gives a very readable tree-shaped visual overview of how your group and child group structures connect:

ansible-inventory -i inventory/production/ --graph

The output maps the hierarchical relationships:

@all:
  |--@datacenter:
  |  |--@webservers:
  |  |  |--web-01.unisbadri.com
  |  |  |--web-02.unisbadri.com
  |  |  |--192.168.56.12
  |  |--@dbservers:
  |  |  |--db-primary.unisbadri.com
  |  |  |--db-replica-01.unisbadri.com
  |--@ungrouped:
  |  |--bastion.unisbadri.com

2. Displaying the Entire Inventory Data in JSON Format #

This command is useful when you want to see every variable attached to each host after Ansible finishes the precedence resolution process:

ansible-inventory -i inventory/production/ --list

3. Checking Variables for One Specific Host #

If you want to focus on tracing which variables are active on one target server without being distracted by other servers, use the --host flag:

ansible-inventory -i inventory/production/ --host web-01.unisbadri.com

The output presents all connection variables and custom variables in clean JSON format:

{
    "ansible_user": "ubuntu",
    "http_port": 80,
    "nginx_version": "1.24",
    "ansible_host": "web-01.unisbadri.com"
}

Summary #

  • The Automation Map — Inventory is a declarative representation of target infrastructure that acts as the single source of truth separating data from playbook logic.
  • Two Main Formats — The INI format is recommended for simple projects due to easy manual writing; the YAML format is used for automated system integration.
  • Range Support — Using numerical range syntax [01:50] or alphabetic [a:f] shrinks file size and minimizes typos when registering homogeneous servers.
  • Built-in all & ungrouped Groups — Ansible organizes all hosts under the root all group and puts group-less hosts into the implicit ungrouped group.
  • Limit Flexibility — You can use pattern matching filters in the CLI (like exclusion ! or intersection &) to safely narrow playbook execution scope.
  • Directory Usage — Pointing the inventory parameter to a folder allows splitting host files by team partnership without overlap risk.
  • Effective Diagnostics — The ansible-inventory --graph command must be run before playbook execution to ensure the server relationship structure reads perfectly.

← Previous: Directory Structure Next: Variable →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact