Skip to main content
RunBook Academy

AnsibleIV · Inventory FundamentalsInventory mechanics

INI and YAML inventories, side by side

Foundation⏱ ~20 minbash

What you'll learn

  • Write the same fleet as an INI inventory and as a YAML inventory
  • Prove two inventories equivalent by diffing ansible-inventory --list output
  • Explain why /etc/ansible/hosts as the default is a hazard for production work
  • Set the inventory in a project ansible.cfg so every run in the repository agrees

Prerequisites

Verified against ansible-core 2.21.x · ansible (community package) 14.x · Python (controller) 3.12+ · ansible-lint 26.x · Molecule 26.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-11

Not yet marked complete on this device.

The inventory is the list of machines Ansible is allowed to touch. Every question this course cares about — which hosts will this change, how many, can I stop it — is answered here first, and answered nowhere else.

If you have met Ansible inventory before, you have probably seen a short INI file and moved on. This part goes past that. It owns the resolution rules: how groups nest, where variable files are searched for, how multiple sources merge, and which value wins when two of them disagree. Those rules are where real incidents come from, and none of them are visible in a ten-line example.

The same fleet, twice

Five hosts: two web servers, two databases, one cache. The web servers and databases are production; the cache is not. The databases have addresses that differ from their names.

In INI:

# inventory/hosts.ini
[webservers]
web01.example.com
web02.example.com

[databases]
db01.example.com ansible_host=192.0.2.31
db02.example.com ansible_host=192.0.2.32

[cache]
cache01.example.com

[production:children]
webservers
databases

[production:vars]
ntp_server=ntp.example.com
environment_name=production

In YAML:

# inventory/hosts.yml
all:
  children:
    cache:
      hosts:
        cache01.example.com:
    production:
      children:
        webservers:
          hosts:
            web01.example.com:
            web02.example.com:
        databases:
          hosts:
            db01.example.com:
              ansible_host: 192.0.2.31
            db02.example.com:
              ansible_host: 192.0.2.32
      vars:
        ntp_server: ntp.example.com
        environment_name: production

Read the structural difference, because it is the thing that trips people converting between them.

INI is flat with cross-references. Groups are declared at the top level in any order, and [production:children] names groups that already exist elsewhere in the file. The nesting is a relationship between independently declared groups.

YAML is a tree. A group is nested inside its parent. There is no separate declaration and reference — a group appears once, in its place in the hierarchy. webservers is written inside production because that is what being a child of production means.

That difference is why a mechanical INI-to-YAML conversion so often changes the group tree without anyone noticing. Which is why you prove it rather than eyeball it.

Proving they are the same

ansible-inventory --list renders the fully resolved inventory as JSON: every group, every host, every variable. Two inventories that produce identical --list output are identical as far as Ansible is concerned, regardless of format.

Read-only / Safethe equivalence test
ansible-inventory -i inventory/hosts.ini --list > /tmp/ini.json
ansible-inventory -i inventory/hosts.yml --list > /tmp/yml.json
diff /tmp/ini.json /tmp/yml.json && echo "IDENTICAL"
Read-only / Safethe INI inventory, fully resolved
$ ansible-inventory -i inventory/hosts.ini --list
{
  "_meta": {
      "hostvars": {
          "db01.example.com": {
              "ansible_host": "192.0.2.31",
              "environment_name": "production",
              "ntp_server": "ntp.example.com"
          },
          "db02.example.com": {
              "ansible_host": "192.0.2.32",
              "environment_name": "production",
              "ntp_server": "ntp.example.com"
          },
          "web01.example.com": {
              "environment_name": "production",
              "ntp_server": "ntp.example.com"
          },
          "web02.example.com": {
              "environment_name": "production",
              "ntp_server": "ntp.example.com"
          }
      },
      "profile": "inventory_legacy"
  },
  "all": {
      "children": [
          "ungrouped",
          "cache",
          "production"
      ]
  },
  "cache": {
      "hosts": [
          "cache01.example.com"
      ]
  },
  "databases": {
      "hosts": [
          "db01.example.com",
          "db02.example.com"
      ]
  },
  "production": {
      "children": [
          "webservers",
          "databases"
      ]
  },
  "webservers": {
      "hosts": [
          "web01.example.com",
          "web02.example.com"
      ]
  }
}

Three things in that output are worth noticing now, because each one becomes a lesson later in this part.

  • cache01.example.com has no variables at all. The ntp_server and environment_name set on production did not reach it, because it is not in production. Group variables flow to members, and only to members.
  • all contains ungrouped, a group nobody wrote. So does every inventory ever written.
  • Group variables are already flattened onto the hosts. By the time --list renders, the question “which group did this value come from” has been answered and discarded. When two groups disagree, this output shows you the winner and not the argument.

Which format to choose

Both are supported, both are current, and neither is deprecated.

YAML, for a new repository. It is the same language as your playbooks and your variable files, so there is one syntax to know rather than two. It nests naturally, which matters once the group tree is more than one level deep. It handles structured variables — lists, dicts — that INI cannot express without workarounds. And it is what the documentation examples increasingly use.

INI, when you inherit it. It is more compact for a flat fleet, non-programmers read it comfortably, and there is no reason to convert a working inventory for aesthetics. You will meet it; be able to read it.

The one place the choice is forced is structured data. In YAML a group variable can be a list or a mapping directly. In INI every value is a string unless you write it as inline JSON, which works and is unpleasant to read. If your group variables are anything but scalars, that is a reason to prefer YAML — but the better answer is usually to move them out of the inventory file entirely and into group_vars/, which is the next lesson but one.

Never rely on the default inventory

Ansible has a default inventory path, and it is a hazard for production work:

Read-only / Safethe default nobody chose
$ ansible-config dump | grep DEFAULT_HOST_LIST
DEFAULT_HOST_LIST(default) = ['/etc/ansible/hosts']

That file is outside your repository. It is therefore outside your pull requests, outside your review, outside your history, and outside whatever the last person who touched the controller did to it.

The project config is the better of the two, because it makes the correct behaviour the default for everyone rather than a thing each person must remember:

# ansible.cfg, at the root of the repository
[defaults]
inventory = inventory/production
Read-only / Safeconfirm which inventory is actually in effect
ansible --version | head -2
ansible-config dump --only-changed | grep DEFAULT_HOST_LIST

Remember from the controller part that a project ansible.cfg is ignored entirely if the directory is world-writable, and that the fallback when it is ignored is exactly the /etc/ansible/hosts problem above. The two failures compose, and the symptom is a run that targets either nothing or the wrong thing.

Knowledge check

Knowledge check · 4 questions

  1. Q1. You have converted an INI inventory to YAML by hand. What is the correct way to confirm the conversion is faithful?

  2. Q2. Which statements about the default inventory path are accurate? Select all that apply.

  3. Q3. A file named inventory/hosts.yml actually contains INI-formatted content. What happens?

  4. Q4. An inventory typo that drops a host out of a group is more dangerous than one that adds a host which does not exist.

Passing score: 75%. Answers are checked in this browser.