Ansible OEL 8 DevOps · OEL 8 · Inventory

AnsibleStatic Inventory — INI and YAML

Two file formats for declaring your hosts and groups, when each one wins, the rules around groups-of-groups, and the conventions that keep a multi-environment inventory tidy.

A static inventory is a plain file you commit to git that tells Ansible which hosts exist and how to group them. Ansible supports two formats: INI (compact, fastest to write) and YAML (more structured, easier to nest deeply).

Two ways to write the same inventory INI format · hosts.ini [mysql_primary] db1.example.com [mysql_replica] db2.example.com db3.example.com [mysql:children] mysql_primary mysql_replica [mysql:vars] ansible_user=deploy YAML format · hosts.yml all: children: mysql: children: mysql_primary: hosts: db1.example.com: mysql_replica: hosts: db2.example.com: db3.example.com: vars: ansible_user: deploy

INI is what most introductory tutorials show. It's the most compact way to spell out hosts and groups when your inventory is mostly flat:

INI — production/hosts.ini
# A group is declared with [name] — every line below until the next [...] is a host
[mysql_primary]
db1.example.com

[mysql_replica]
db2.example.com
db3.example.com

# A "group of groups" — combines two groups into a parent
[mysql:children]
mysql_primary
mysql_replica

# A standalone tier
[webservers]
web1.example.com ansible_user=alice    # per-host vars on the same line
web2.example.com

# Group-level variables — applied to every host in the group
[mysql:vars]
ansible_user=deploy
mysql_port=3306

# Variables that apply to every host in the inventory
[all:vars]
ansible_python_interpreter=/usr/bin/python3
timezone=UTC
💡 Tip: Use [group:children] for groups-of-groups and [group:vars] for group variables. These suffix conventions are part of the INI format itself — they don't work in YAML, where you express the same thing with nested structure.

YAML is heavier syntactically but supports arbitrary nesting cleanly. Pick it when you have multiple levels of group-of-groups or when you want host- and group-vars inline:

YAML — production/hosts.yml
---
all:
  vars:
    ansible_python_interpreter: /usr/bin/python3
    timezone: UTC
  children:
    mysql:
      vars:
        ansible_user: deploy
        mysql_port: 3306
      children:
        mysql_primary:
          hosts:
            db1.example.com:
        mysql_replica:
          hosts:
            db2.example.com:
            db3.example.com:
    webservers:
      hosts:
        web1.example.com:
          ansible_user: alice
        web2.example.com:
INIYAML
CompactnessWins for flat layoutsVerbose for the same data
Nested groupsReads OK at 2 levels, awkward at 3+Stays readable at any depth
Per-host varsSame line as host (KEY=VAL)Indented dict under the host
Group vars location[group:vars] blocksvars: key inline
Comments##
ValidationLoose — typos parse silentlyStrict YAML parser catches typos
BASH — using the inventory
# Single file
ansible-playbook site.yml -i hosts.ini

# Multiple files / directories — Ansible merges them
ansible-playbook site.yml -i hosts.ini -i extra.yml

# Whole directory — every file inside is treated as inventory
ansible-playbook site.yml -i inventories/production/

# Set as default in ansible.cfg (no -i flag needed for this project)
echo "[defaults]
inventory = inventories/production/" > ansible.cfg
ansible-playbook site.yml
BASH — what does Ansible see?
# List every host Ansible will target
ansible-inventory -i inventories/production/ --list

# Tree view of groups and their members
ansible-inventory -i inventories/production/ --graph

# Resolve all variables for one specific host
ansible-inventory -i inventories/production/ --host db1.example.com
💡 Tip: Run ansible-inventory --graph any time you change the file. It shows the resolved tree and surfaces typos like my_sql vs mysql immediately.

The convention for managing more than one environment is to give each its own inventory directory and pick at runtime with -i:

DIR — multi-environment layout
inventories/
├── dev/
│   ├── hosts.ini
│   ├── group_vars/
│   └── host_vars/
├── staging/
│   ├── hosts.ini
│   ├── group_vars/
│   └── host_vars/
└── production/
    ├── hosts.ini
    ├── group_vars/
    └── host_vars/

# Run a playbook against staging
ansible-playbook site.yml -i inventories/staging/

# Or against production
ansible-playbook site.yml -i inventories/production/
⚠ Warning: Don't keep dev / staging / production groups in the same inventory file with conditionals — it's a recipe for accidentally deploying staging changes to production. Separate inventory directories give you a hard boundary.

Special variables override how Ansible connects to a specific host:

VariablePurpose
ansible_hostReal DNS name or IP (when inventory name is a label)
ansible_userSSH user for that host
ansible_portSSH port (default 22)
ansible_ssh_private_key_filePath to a specific private key
ansible_python_interpreterPath to Python 3 on the remote
ansible_connectionssh / local / paramiko / winrm
INI — per-host overrides
[bastion]
bastion.example.com ansible_user=admin ansible_port=2222

[behind_bastion]
db1 ansible_host=10.0.1.10 ansible_ssh_common_args="-o ProxyJump=bastion.example.com"
db2 ansible_host=10.0.1.11 ansible_ssh_common_args="-o ProxyJump=bastion.example.com"
✅ Tip: Once your inventory is solid, every later page in this series — vault, dynamic discovery, custom plugins — builds on it. It's worth getting right early.