How Ansible Auto-Loads Variables
When you point Ansible at an inventory directory, it automatically picks up two
companion folders next to the inventory file: group_vars/ and host_vars/.
The filename inside those folders matches a group name or host name from the inventory.
The Lookup Rules
- For every host in inventory, Ansible loads
group_vars/all.ymlfirst. - Then it loads
group_vars/<group>.ymlfor every group the host belongs to. - Sub-group variables override parent-group variables.
- Finally
host_vars/<hostname>.ymlwins over any group_vars.
A Real Inventory Layout
DIR — production inventory
production/
├── hosts.ini
├── group_vars/
│ ├── all.yml # applies to every host
│ ├── databases.yml # applies to all DB hosts
│ ├── mysql.yml # applies to MySQL hosts only
│ ├── mysql_primary.yml # applies to mysql_primary group
│ ├── mysql_replica.yml # applies to mysql_replica group
│ └── webservers.yml # applies to web hosts
└── host_vars/
├── db1.example.com.yml # overrides for db1
├── db2.example.com.yml # overrides for db2
└── web1.example.com.yml
INI — production/hosts.ini
[mysql_primary]
db1.example.com
[mysql_replica]
db2.example.com
db3.example.com
[mysql:children]
mysql_primary
mysql_replica
[databases:children]
mysql
[webservers]
web1.example.com
web2.example.com
[all:vars]
ansible_user=deploy
Sample Vars Files
YAML — group_vars/all.yml
---
# Defaults that apply to every host across the entire inventory
ansible_python_interpreter: /usr/bin/python3
ntp_servers:
- 0.pool.ntp.org
- 1.pool.ntp.org
timezone: UTC
admin_users:
- alice
- bob
YAML — group_vars/databases.yml
---
# Applied to every host in [databases] (i.e. all MySQL hosts via the children chain)
backup_retention_days: 14
firewall_open_ports:
- 22
- 3306
db_admin_email: dba@example.com
YAML — group_vars/mysql_primary.yml
---
# Sub-group — applied only to db1.example.com
mysql_role: primary
server_id: 1
mysql_log_bin: true
mysql_max_connections: 500
YAML — host_vars/db1.example.com.yml
---
# Per-host overrides — beat all group_vars
mysql_innodb_buffer_pool_size: "8G" # bigger box, more buffer pool
custom_motd: "PRIMARY DB · do not run heavy queries here"
Splitting Vars Across Multiple Files
For larger inventories, replace a single file with a directory of the same name —
Ansible loads every .yml inside it (alphabetical order):
DIR — group_vars as directories
group_vars/
├── databases/
│ ├── connection.yml # connection settings
│ ├── tuning.yml # buffer pools, max_connections, etc.
│ └── vault.yml # encrypted secrets (Ansible Vault)
└── webservers/
├── nginx.yml
└── tls.yml
💡 Tip: This pattern is great for separating plain config from secrets: keep
vault.yml encrypted with Ansible Vault, leave the others readable. Both files are auto-loaded. Multi-Environment Layout
The convention for multiple environments is to put each one in its own folder:
DIR — multi-environment
inventories/
├── dev/
│ ├── hosts.ini
│ ├── group_vars/
│ │ └── databases.yml
│ └── host_vars/
├── staging/
│ ├── hosts.ini
│ ├── group_vars/
│ │ └── databases.yml
│ └── host_vars/
└── production/
├── hosts.ini
├── group_vars/
│ └── databases.yml
└── host_vars/
# Run against an environment by pointing -i at its directory
ansible-playbook -i inventories/staging site.yml
ansible-playbook -i inventories/production site.yml
✅ Tip: Same playbook, three environments, zero per-environment branching in the playbook itself — variables alone differentiate them. This is the Ansible way.