Inventory #

Before Ansible can perform any automation on your infrastructure, it needs to know where to connect and which servers are the execution targets. This information source is defined in the inventory. The inventory isn’t just a list of IP addresses or server hostnames — it’s a structured logical map that determines how servers are grouped, what configuration variables are attached to each entity, and how hierarchical relationships between servers are built. A solid understanding of inventory helps you avoid configuration management technical debt from the start.

Defining the Target Server Registry #

Essentially, the inventory acts as Ansible’s target server database registry. When you run a playbook execution command, you specify a target parameter (like hosts: webservers). Ansible then matches that keyword against the inventory file to get the right SSH connection address and credentials for the relevant Managed Nodes.

Without a structured inventory, your automation loses direction and can’t run modularly. The inventory lets you separate the physical infrastructure definition from the playbook task logic, so your automation code becomes reusable across environments (for example from dev, staging, to production).


INI vs YAML Format Comparison #

Ansible natively supports two formats for writing static inventory files: INI and YAML. Both formats serve different purposes depending on the complexity of the infrastructure you manage.

The INI format has a very compact structure that’s quick to read, and is widely used for small to mid-sized projects. The YAML format is more expressive, strictly indented and structured, and well-suited for large-scale infrastructure that requires nested variables.

Let’s compare production-ready inventory files written in both formats:

1. Example Static Inventory File (INI Format) #

# /etc/ansible/hosts or production.ini
[webservers]
web-prod-01 ansible_host=192.168.10.11 ansible_user=ubuntu
web-prod-02 ansible_host=192.168.10.12 ansible_user=ubuntu

[dbservers]
db-prod-01 ansible_host=192.168.10.20 ansible_user=admin db_port=5432
db-prod-02 ansible_host=192.168.10.21 ansible_user=admin db_port=5432

[production:children]
webservers
dbservers

[production:vars]
env=production
ansible_connection=ssh

2. Example Static Inventory File (YAML Format) #

# production.yaml
all:
  children:
    webservers:
      hosts:
        web-prod-01:
          ansible_host: 192.168.10.11
          ansible_user: ubuntu
        web-prod-02:
          ansible_host: 192.168.10.12
          ansible_user: ubuntu
    dbservers:
      hosts:
        db-prod-01:
          ansible_host: 192.168.10.20
          ansible_user: admin
          db_port: 5432
        db-prod-02:
          ansible_host: 192.168.10.21
          ansible_user: admin
          db_port: 5432
      vars:
        db_version: 15.2
  vars:
    env: production
    ansible_connection: ssh

Hierarchical Grouping Structure (:children) #

The main strength of the Ansible inventory lies in its ability to group servers flexibly. Grouping helps you classify servers by functional role (like [webservers], [dbservers]) or by geographic location (like [jakarta], [singapore]).

You can build group hierarchies using the children parameter. This lets a parent group inherit all members and variables from child groups.

[web-jakarta]
web-jkt-01.example.com
web-jkt-02.example.com

[web-singapore]
web-sgp-01.example.com

# Parent group combines both regions
[webservers:children]
web-jakarta
web-singapore

With the structure above, if you run a command targeting hosts: webservers, Ansible executes tasks in parallel on all three servers in Jakarta and Singapore. However, if you want region-specific maintenance for Jakarta, just narrow the target by calling hosts: web-jakarta.

You can also use logical intersection operators on the command line to filter targets dynamically:

# Execute the playbook only on servers that are in the webservers group AND located in the jakarta region
ansible-playbook -i inventory.ini site.yml --limit "webservers:&web-jakarta"

Managing Variables in Inventory: Best Practices #

The inventory lets you define special variables attached to specific hosts (Host Variables) or specific groups (Group Variables). These variables are often used to store custom ports, application directory paths, or OS configuration parameters.

Anti-Pattern: Writing Variables Directly in the Inventory File #

It’s very tempting to write variables right next to the hostname like the INI example above (db-prod-01 ansible_host=... db_port=5432). However, as the number of variables grows (for example credentials, API keys, feature flags), your inventory file becomes very long, hard to read, and prone to format errors.

Practical Solution: Using group_vars and host_vars Directories #

Ansible’s best practice recommendation is to separate variable definitions from the physical inventory file into structured folders called group_vars/ and host_vars/. These folders are placed in the same directory as the inventory file or your playbook file.

Here’s the recommended Ansible project directory layout:

ansible-project/
  ├── production-inventory.ini
  ├── site.yml
  ├── group_vars/
  │     ├── all.yml           # Variables that apply to all hosts
  │     ├── webservers.yml    # Variables specific to the webservers group
  │     └── dbservers.yml     # Variables specific to the dbservers group
  └── host_vars/
        ├── web-prod-01.yml   # Variables specific to the web-prod-01 host only
        └── db-prod-01.yml    # Variables specific to the db-prod-01 host only

Inside those yml files, you write variables using clean standard YAML format:

# group_vars/webservers.yml
---
http_port: 80
nginx_max_clients: 1024
nginx_worker_processes: auto

Ansible automatically reads these folders by matching the group or host names registered in your inventory file.


How Dynamic Inventory Works #

In the modern cloud computing era, where infrastructure is elastic, static inventories have a major limitation. Virtual machines (VMs) can be created and destroyed automatically by autoscaling systems based on traffic load. Manually updating the inventory file every time an IP address changes is an impossible job.

To solve this, Ansible provides the Dynamic Inventory feature. Instead of reading a static text file, Ansible uses a special inventory plugin that contacts the cloud provider’s API in real time to automatically build the list of target servers along with their variables.

Here’s a diagram of the dynamic inventory workflow:

flowchart TD
    Run["1. You run the ansible-playbook command"] --> Plugin["2. Inventory plugin is activated (e.g. aws_ec2)"]
    Plugin --> API["3. Makes an encrypted API request to the Cloud Provider"]
    API --> Respond["4. Cloud Provider returns metadata of all active VMs"]
    Respond --> Parse["5. Plugin builds the inventory JSON and maps groups based on Cloud Tags"]
    Parse --> Exec["6. Ansible executes the playbook on those dynamic servers"]

Example AWS EC2 Inventory Plugin Configuration: #

You use a configuration file ending in aws_ec2.yml to trigger the AWS dynamic inventory call:

# demo_aws_ec2.yml
plugin: amazon.aws.aws_ec2
regions:
  - ap-southeast-3 # Jakarta Region
filters:
  # Only fetch VMs with 'running' status
  instance-state-name: [ running ]
keyed_groups:
  # Auto-create groups based on the 'Role' Tag in the EC2 console
  - key: tags.Role
    prefix: role
  # Create groups based on Availability Zone
  - key: placement.availability_zone
    prefix: zone

When you run the playbook, Ansible automatically creates groups like role_web or zone_ap_southeast_3a based on real metadata pulled from the AWS console.


Built-in Automatic Groups: all and ungrouped #

Every time you trigger an Ansible command, the system implicitly creates two main built-in groups that you never need to declare physically in the inventory file:

  1. all: The global container group containing every server registered in your inventory. You can use the hosts: all keyword in a playbook when you want to apply base configuration (like timezone alignment or utility installation) to every server without exception.
  2. ungrouped: A special group holding servers you register in the inventory without placing them into any group classification.

Inventory Management and Verification Commands #

To make sure your static or dynamic inventory structure is free of parser errors before running a real playbook scenario, you can use these Ansible CLI verification commands:

1. Displaying the Group Relationship Graph #

The --graph command is very useful for visualizing the parent-child group hierarchy in a structured way in the terminal:

ansible-inventory -i production-inventory.ini --graph

The generated graph output will look like this:

@all:
  |--@production:
  |  |--@webservers:
  |  |  |--web-prod-01
  |  |  |--web-prod-02
  |  |--@dbservers:
  |  |  |--db-prod-01
  |  |  |--db-prod-02
  |--@ungrouped:

2. Exporting the Server List to JSON Format #

The --list command is used to view the entire server list along with related host/group variables in complete JSON format:

ansible-inventory -i production-inventory.ini --list

Summary #

  • Inventory acts as the target map — Contains the Managed Node list, logical grouping, and specific variables that are the targets of Ansible automation.
  • INI vs YAML formats — INI is compact and easy to understand for small scale, while YAML is ideal for large-scale nested group structures.
  • Group Hierarchy (:children) — Makes variable inheritance and merging multiple child groups into one unified parent group easy.
  • Variable Separation (Best Practice) — Avoid writing variables directly in the inventory file; separate them into group_vars/ and host_vars/ folders.
  • Dynamic Inventory — The absolute automation solution for dynamic cloud infrastructure by calling provider APIs in real time through inventory plugins.

← Previous: Node Next: Module →

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