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).
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:
# 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
[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:
---
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:
| INI | YAML | |
|---|---|---|
| Compactness | Wins for flat layouts | Verbose for the same data |
| Nested groups | Reads OK at 2 levels, awkward at 3+ | Stays readable at any depth |
| Per-host vars | Same line as host (KEY=VAL) | Indented dict under the host |
| Group vars location | [group:vars] blocks | vars: key inline |
| Comments | # | # |
| Validation | Loose — typos parse silently | Strict YAML parser catches typos |
# 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
# 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
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:
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/
Special variables override how Ansible connects to a specific host:
| Variable | Purpose |
|---|---|
ansible_host | Real DNS name or IP (when inventory name is a label) |
ansible_user | SSH user for that host |
ansible_port | SSH port (default 22) |
ansible_ssh_private_key_file | Path to a specific private key |
ansible_python_interpreter | Path to Python 3 on the remote |
ansible_connection | ssh / local / paramiko / winrm |
[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"