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
  • web1 is the inventory hostname (used in output and host_vars); ansible_host is the real address.
  • [production:children] creates a group of groups.
  • Every host is automatically in all; hosts in no group are in ungrouped.

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.yml with Ansible Vault.

Next: writing playbooks and roles.

  • Ansible
  • Automation
  • DevOps