The inventory tells Ansible which servers exist, how to reach them, and how they’re grouped. A well-organised inventory makes playbooks simpler: you target web or production instead of lists of IPs.
INI format
# inventory.ini
[web]
web1 ansible_host=203.0.113.10
web2 ansible_host=203.0.113.11
[db]
db1 ansible_host=203.0.113.20 ansible_port=2222
[hk]
hk-web1 ansible_host=198.51.100.5
[production:children]
web
db
[all:vars]
ansible_user=deploy
ansible_python_interpreter=/usr/bin/python3
web1is the inventory hostname (used in output andhost_vars);ansible_hostis the real address.[production:children]creates a group of groups.- Every host is automatically in
all; hosts in no group are inungrouped.
YAML format
The same inventory in YAML — easier for nested structures:
# inventory.yml
all:
vars:
ansible_user: deploy
children:
web:
hosts:
web1: { ansible_host: 203.0.113.10 }
web2: { ansible_host: 203.0.113.11 }
db:
hosts:
db1:
ansible_host: 203.0.113.20
ansible_port: 2222
production:
children:
web: {}
db: {}
Connection variables
| Variable | Meaning |
|---|---|
ansible_host |
Address to connect to |
ansible_port |
SSH port |
ansible_user |
SSH user |
ansible_ssh_private_key_file |
Key to use |
ansible_become / ansible_become_user |
sudo settings |
ansible_python_interpreter |
Python on the target |
ansible_connection |
ssh (default), local, docker… |
Settings in ~/.ssh/config (ports, keys, jump hosts) are respected too, so you can keep connection details there and use host aliases in the inventory. See SSH config file.
group_vars and host_vars
Keep variables out of the inventory file in directories next to it (or next to the playbook):
inventory.yml
group_vars/
all.yml
web.yml
production/
main.yml
vault.yml # encrypted secrets
host_vars/
db1.yml
# group_vars/web.yml
nginx_worker_connections: 2048
site_domain: example.com
Precedence (simplified, lowest to highest): all group vars → parent group vars → child group vars → host vars → play vars → extra vars (-e). When the same variable is set in two groups at the same level, results can surprise you — keep each variable defined in one obvious place.
Patterns: targeting hosts
ansible web -m ping # one group
ansible 'web:db' -m ping # union
ansible 'production:&hk' -m ping # intersection: production AND hk
ansible 'web:!web2' -m ping # exclusion
ansible 'web*' -m ping # wildcard on names
ansible-playbook site.yml --limit web1
ansible-playbook site.yml --limit @failed.retry
Inspecting the inventory
ansible-inventory --graph
ansible-inventory --list -y
ansible-inventory --host web1 # merged variables for one host
--host is the best way to debug “why does this host get that value?”.
Multiple environments
Separate inventories per environment keep staging and production apart:
inventories/
staging/
hosts.yml
group_vars/
production/
hosts.yml
group_vars/
ansible-playbook -i inventories/staging site.yml
ansible-playbook -i inventories/production site.yml
It’s much harder to accidentally run against production when you have to name it explicitly.
Dynamic inventories
For cloud environments or many servers, generate the inventory automatically with inventory plugins (AWS, Azure, GCP, Hetzner, Proxmox, Netbox and others) or a script that outputs JSON. A YAML config file selects the plugin:
# netbox.yml
plugin: netbox.netbox.nb_inventory
api_endpoint: https://netbox.example.com
For a handful of VPS servers, a static YAML inventory in Git is simpler and perfectly fine.
Tips
- Use meaningful inventory hostnames (
web1-la,db1-hk) rather than IPs. - Group by role (
web,db), environment (staging,production) and location (usa,hk,jp) — hosts can belong to several groups. - Encrypt secrets in
group_vars/*/vault.ymlwith Ansible Vault.
Next: writing playbooks and roles.