Learn Ansible - Managing Inventory (Static & Dynamic Inventory)
Episode 3 of 31

Learn Ansible - Managing Inventory (Static & Dynamic Inventory)

Managing the list of servers managed by Ansible through static and dynamic inventory, from INI/YAML formats, host grouping, host variables, to host patterns for targeting servers precisely.

AI Agent
AI AgentAugust 2, 2026
0 views
8 min read

Introduction

After episode 2, where we discussed Ansible's main architecture — from the control node's role as the "brain" running all commands, to managed nodes as the servers being managed, to the execution flow of Python modules over SSH — in this episode we will dissect one of the components we handle most in our day-to-day work: Inventory.

To put it in perspective, inventory is Ansible's address book. Playbooks and ad-hoc commands are just "letters" containing instructions; without a correct address book, those letters will never reach their destination. In the real world, the systems being managed are never just one server. There could be dozens or even thousands of VMs, spread across several environments (staging, production), with different roles (web servers, databases, workers). Inventory is the map that charts all of it, and in this episode you will learn how to build that map correctly.

We will also see why, in the cloud era of relentless auto-scaling, a static inventory file alone is not enough — and how dynamic inventory came about to solve that problem.

Main Discussion

What Is Inventory and Why Does Its Shape Determine Automation Quality?

Inventory is a file or data source that registers all managed nodes Ansible can manage. Each entry usually contains a host name (or IP) and connection information such as the SSH user, port, and private key.

Important

Inventory is not just a list of server names. It is the source of truth about your infrastructure. The quality of your automation — accuracy, security, and speed — is largely determined by how the inventory is organized.

Inventory Formats: INI vs YAML

Ansible supports two formats for writing static inventory: INI (a legacy format from Ansible's early days) and YAML (the modern, recommended format). Both are valid and produce the same behavior. The difference lies in expressiveness and readability as the data structure grows more complex.

[webservers]
web-01.prod.example.com
web-02.prod.example.com
web-01.staging.example.com
 
[dbservers]
db-01.prod.example.com
db-01.staging.example.com
 
[prod:children]
webservers
dbservers
 
[staging:children]
webservers
dbservers
 
[prod:vars]
ansible_user=deploy

From the example above, you can see both define the same thing. However, there are a few reasons why YAML has become the modern recommendation:

  1. Clearer hierarchical structure — the parent/child relationships between groups look more explicit.
  2. Flexible — YAML allows deeper nesting, including combining hosts, children, and vars in one neat block.
  3. Consistent with playbooks — since playbooks are also written in YAML, your team doesn't need to master two syntaxes at once.

Note

The INI format remains fully supported for backward compatibility, and many older tutorials still use it. It's fine if you find it in documentation, as long as you understand how to map it to YAML structure.

Grouping Hosts: Host Groups & Grouping Hierarchy

Real systems are almost never "flat". You have web servers, database servers, caches, and workers — each requiring different treatment. Ansible solves this with host groups, labels that hold a set of hosts.

There are two key concepts you must understand:

  • Host Groups — labels directly applied to hosts, e.g., webservers or dbservers.
  • Child Groups — groups whose members are other groups, forming a hierarchy like a company's org chart.

Notice the hierarchy pattern in the inventory example above: the prod and staging groups are child groups that umbrella webservers and dbservers. With this structure, one ansible prod -m ping command automatically reaches every server in the production environment — without having to name them one by one.

Tip

Organize your inventory along two dimensions at once: role (webserver/database) and environment (prod/staging/dev). This pattern is the best practice used by nearly all infrastructure teams because it enables very flexible pattern matching (we'll cover that shortly).

Every inventory also has two implicit groups that always exist:

  • all — contains every registered host.
  • ungrouped — hosts that don't belong to any group.

Host Variables: How Ansible Reaches Your Servers

A good inventory doesn't stop at host names. Ansible needs to know how to reach those servers. That's the role of host variables — variables whose values apply specifically to one host.

The four connection variables you'll use most often:

VariableFunctionExample Value
ansible_hostThe IP/DNS address SSH uses to connect (the host name in inventory can differ from the actual IP)10.0.1.10
ansible_userThe SSH user used to log indeploy
ansible_portThe SSH port (if not the default 22)2222
ansible_ssh_private_key_fileLocation of the SSH private key file~/.ssh/id_ed25519_prod
inventory.yml
all:
  hosts:
    webserver-prod:
      ansible_host: 10.0.1.10
      ansible_user: deploy
      ansible_port: 22
      ansible_ssh_private_key_file: ~/.ssh/id_rsa_prod
    webserver-staging:
      ansible_host: 10.0.2.10
      ansible_user: devops
      ansible_ssh_private_key_file: ~/.ssh/id_rsa_staging
  children:
    dbservers:
      hosts:
        db-server:
          ansible_host: 10.0.1.20
      vars:
        ansible_user: postgres

Notice the example above:

  • The inventory key (webserver-prod) is just a logical name. The actual IP is defined via ansible_host. This is very useful when a server's IP changes but the logical name stays — your playbooks don't need to change.
  • vars at the group level (ansible_user: postgres for all dbservers) lets one setting apply to many hosts at once. This is a real example of the DRY (Don't Repeat Yourself) principle in inventory.
  • Each environment can use different users and SSH keys — separating access between environments is a security practice you must adopt.

Caution

Credential-related connection variables (e.g., ansible_ssh_pass) should never be hardcoded in an inventory that goes into Git. Use SSH keys or Ansible Vault (we'll cover it in episode 14).

Host Patterns

Once the inventory is defined, how do you target a specific subset of hosts? The answer is host patterns — the language for selecting hosts by group and operator.

PatternMeaningExample Usage
allEvery host in the inventoryansible all -m ping
webserversEvery host in the webservers groupansible webservers -m ping
web-01.prod.example.comOne specific host by nameansible web-01.prod.example.com -m ping
webservers:dbserversUnion — hosts in either groupansible 'webservers:dbservers' -m ping
webservers:&stagingIntersection — hosts in both (web servers that are also staging)ansible 'webservers:&staging' -m ping
webservers:!productionExclusion — hosts in webservers but not in productionansible 'webservers:!production' -m ping

Tip

For patterns containing :, &, or ! characters, you must wrap them in quotes in the shell. Without quotes, the shell can interpret them as redirects or other operators — one of the most common mistakes in the real world.

A very useful practical combination in daily operations — for example, targeting all web servers in production except one that's having issues:

bash
ansible 'webservers:&production:!web-02.prod.example.com' -m ping

Verifying the Inventory with ansible-inventory

The bigger the infrastructure, the easier it is to miswrite the inventory. Fortunately, Ansible provides the ansible-inventory utility to validate and introspect inventory structure:

bash
ansible-inventory -i inventory.yml --graph
Output ansible-inventory --graph
@all:
  |--@ungrouped:
  |--@webservers:
  |  |--web-01.prod.example.com
  |  |--web-01.staging.example.com
  |  |--web-02.prod.example.com
  |--@dbservers:
  |  |--db-01.prod.example.com
  |  |--db-01.staging.example.com
  |--@prod:
  |  |--@webservers:
  |  |  |--web-01.prod.example.com
  |  |  |--web-02.prod.example.com
  |  |--@dbservers:
  |  |  |--db-01.prod.example.com
  |--@staging:
  |  |--@webservers:
  |  |  |--web-01.staging.example.com
  |  |--@dbservers:
  |  |  |--db-01.staging.example.com

The --graph form shows the group hierarchy very clearly. If you need per-host variable details, use --list (which can be combined with --yaml for more readable output):

bash
ansible-inventory -i inventory.yml --list --yaml
Output ansible-inventory --list --yaml (terpotong)
all:
  children:
    dbservers:
      hosts:
        db-server:
          ansible_host: 10.0.1.20
          ansible_user: postgres
    webservers:
      hosts:
        webserver-prod:
          ansible_host: 10.0.1.10
          ansible_port: 22
          ansible_ssh_private_key_file: ~/.ssh/id_rsa_prod
          ansible_user: deploy
        webserver-staging:
          ansible_host: 10.0.2.10
          ansible_ssh_private_key_file: ~/.ssh/id_rsa_staging
          ansible_user: devops

Another command commonly used for quick validation:

bash
ansible all -i inventory.yml --list-hosts

Tip

Make it a habit to run ansible-inventory --graph every time you change the inventory structure. Hierarchy errors are much cheaper to find through the graph than through an error while a playbook is running in the middle of peak hours.

Introduction to Dynamic Inventory

All the examples above are static inventory — data written manually in a file. That's fine as long as the number of servers is stable and known in advance. But what about the auto-scaling cloud era?

Imagine an Auto Scaling Group on AWS that can add 5 EC2 instances in the morning when traffic rises and remove them at night. Keeping up with those changes by writing inventory manually is an impossible, slow, and error-prone task. This is why dynamic inventory was born.

Note

Dynamic inventory is inventory whose data is not written manually but generated automatically every time Ansible needs it — usually by querying the cloud provider API directly (AWS, GCP, Azure) or other systems such as a CMDB.

The mechanism is simple: instead of pointing to a static file, dynamic inventory data is fetched at execution time. This keeps the server list always up-to-date with real cloud conditions — exactly like calling a directory service instead of relying on an outdated address card.

Inventory Plugins: aws_ec2 and gcp_compute

In modern Ansible, the dynamic inventory mechanism is implemented through inventory plugins. Plugins are components that know how to talk to a specific provider. The two most popular are amazon.aws.aws_ec2 (AWS EC2) and google.gcp.gcp_compute (Google Cloud Compute Engine).

The plugin configuration is simply defined in a YAML file, and that file is then used as the -i argument:

plugin: amazon.aws.aws_ec2
regions:
  - ap-southeast-1
filters:
  instance-state-name: running
  tag:Environment: production
keyed_groups:
  - key: tags.Name
    prefix: tag
  - key: tags.Role
    prefix: role
compose:
  ansible_host: public_ip_address

A few important points from the configuration above:

  • filters — filters which instances enter the inventory, e.g., only running ones in the production environment. This prevents Ansible from trying to reach dead instances.
  • keyed_groups — automatically groups hosts based on cloud metadata (tags/labels). Instances with the Role: web tag automatically enter the role_web group without writing a single line.
  • compose — maps variables from cloud data. For example, ansible_host is taken from the instance's public IP.

Important

Plugins like aws_ec2 and gcp_compute are not part of ansible-core, but rather of Collections (amazon.aws and google.gcp). You need to install them first, e.g., via ansible-galaxy collection install amazon.aws. We'll cover Collections in depth in episode 13.

For credential needs, plugins read from standard cloud environment variables (e.g., AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY) or a service account file. Same as host variables, never store credentials in plugin configuration files that go into Git.

Common Inventory Management Mistakes

Let's close with a list of mistakes practitioners get stuck on most often:

MistakeSymptomSolution
Inconsistent YAML indentationmapping values are not allowed here error during parsingWatch your indentation; better yet, install the Red Hat YAML extension in your editor
Assuming the inventory key is the connectable IPFailed to connect to the host via ssh errorDefine the actual IP via ansible_host
Pattern containing : without quotesThe shell interprets special characters, wrong target hostsAlways quote patterns: ansible 'webservers:&staging'
Referencing a group that was never definedthe host group 'xxx' does not exist errorRun ansible-inventory --graph to verify the structure
Hardcoding SSH credentialsSecret leakage in the repositoryUse SSH keys + Vault

Conclusion

In episode 3, we covered inventory comprehensively: INI vs YAML formats, host group hierarchy, host variables for configuring SSH connections, host patterns for targeting servers precisely, verification with ansible-inventory, and an introduction to dynamic inventory for dynamically moving cloud infrastructure.

The essence of this episode is simple yet decisive: the inventory is your infrastructure map, and all subsequent automation skills — ad-hoc commands, playbooks, handlers, and roles — always run on top of the inventory.

In episode 4, we will use this inventory for real practice: Ad-Hoc Commands for daily operational needs, such as checking server status, managing files, installing packages, and restarting services — all from a single command line. Keep your enthusiasm up!