Ansible Inventory Management: Static, Dynamic, and Variable Precedence Pitfalls
Master Ansible inventory management: static/dynamic files, INI vs YAML, groups, host_vars, group_vars, inventory plugins, and variable precedence.
20+ years shipping production infrastructure and CI/CD at scale. Lessons pulled from things that broke in production.
- ✓Basic programming fundamentals
- ✓A computer with internet access
- ✓Willingness to follow along with examples
Use ansible-inventory --list to verify inventory structure before running playbooks.
Static inventory files (INI or YAML) are fine for small environments; use dynamic inventory plugins for cloud or CMDB sources.
YAML format is preferred over INI for complex inventories due to better readability and nesting support.
Host variables in host_vars/ override group variables from group_vars/.
Group variable precedence: all groups → children groups → parent groups (lower group_vars dirs) → group_vars/all is lowest.
Dynamic inventory scripts must output JSON in the specific format expected by Ansible (e.g., _meta key for hostvars).
Variable precedence from inventory: host_vars > group_vars of host's groups (last group wins) > group_vars/all.
Common gotcha: ansible-playbook -i inventory without --diff may hide variable overrides; use -v to debug.
Imagine you're planning a large family reunion. You have a list of all relatives (the inventory), and you need to send each person a personalized invitation. Some details apply to everyone (like the date and location), some apply to entire families (like dietary preferences for the Smiths), and some are specific to individuals (like a special note for Aunt Carol). Ansible inventory is that list: you define hosts (people), groups (families), and variables (details). Static inventory is like a paper address book — you write everything down manually. Dynamic inventory is like a live phonebook that automatically updates when someone moves. Variable precedence is the rule that decides which detail wins if there's a conflict: a note written directly on a person's page beats a note on the family page, which beats a general note on the front cover.
I'll never forget the Tuesday morning when our production deployment started failing with cryptic SSH key errors. Our playbooks had run fine for months, but suddenly Ansible couldn't connect to a batch of new servers. The error was 'Permission denied (publickey)'. My first assumption was a key rotation gone wrong. But after an hour of head-scratching, I discovered the real culprit: a misconfigured dynamic inventory script that was returning an empty ansible_user for those hosts, causing Ansible to fall back to the current user (which had no access). That incident taught me the hard way how inventory management — especially variable precedence and inventory sources — can silently break deployments.
Ansible inventory is the backbone of configuration management. It defines which hosts to manage, how to group them, and what variables apply. Without a solid understanding of inventory, you'll run into mysterious failures, inconsistent variable resolution, and maintenance nightmares. This article covers everything a beginner needs: static and dynamic inventory files, INI vs YAML formats, groups and children, host_vars and group_vars directories, inventory plugins, and the critical rules of variable precedence across inventory.
By the end, you'll know how to structure your inventory for production, debug common issues, and avoid the pitfalls that tripped me up. We'll use real commands and production-grade examples throughout.
Static Inventory Files: INI vs YAML Format
Static inventory files are the simplest way to define your infrastructure. Ansible supports two formats: INI and YAML. INI is the legacy format, but YAML is now recommended for complex inventories due to its nesting capabilities and readability.
INI Format Example (inventory.ini): ```ini [webservers] web1.example.com ansible_user=deploy web2.example.com
[dbservers] db1.example.com
[production:children] webservers dbservers
[webservers:vars] http_port=80 ```
YAML Format Example (inventory.yaml): ``yaml all: children: webservers: hosts: web1.example.com: ansible_user: deploy web2.example.com: vars: http_port: 80 dbservers: hosts: db1.example.com: production: children: webservers: dbservers: ``
Production Insight: INI format can lead to subtle bugs when using :vars and :children syntax incorrectly. For example, forgetting the :children suffix on a group that contains other groups will silently ignore the group membership. YAML eliminates this ambiguity. In a recent migration, I found a stale INI inventory where a group was misspelled in :children, causing 20 servers to be excluded from deployments for months.
Key Takeaway: For any inventory with more than 10 hosts or nested groups, use YAML format to avoid parsing errors and improve maintainability.
:children exactly match existing group names. Ansible does not warn about missing groups; it simply ignores the line.[newgroup] instead of [newgroup:children] for a parent group. The hosts were listed but never included in the parent. The fix was to switch to YAML and add a CI linting step with ansible-inventory --list.Groups, Children, and Group Hierarchy
Groups are the primary way to organize hosts in Ansible. A group can contain hosts, other groups (via children), or both. Group hierarchy allows you to apply variables to a set of hosts efficiently.
Defining Groups and Children: ``yaml all: children: us_east: children: webservers: hosts: web-east-1: web-east-2: dbservers: hosts: db-east-1: us_west: children: webservers: hosts: web-west-1: dbservers: hosts: db-west-1: ``
Group Resolution: Ansible flattens the group hierarchy at runtime. A host belongs to all groups it is directly or indirectly a member of. For example, web-east-1 belongs to webservers, us_east, and implicitly all.
Variable Inheritance: Variables defined on a group apply to all hosts in that group and its children. If a variable is defined on multiple groups in the hierarchy, the last group (in alphabetical order) wins for that host. This is often confusing; see the variable precedence section.
Production Insight: In a multi-region deployment, we used nested groups for regions and tiers. The problem was that we defined ntp_server on both us_east and webservers groups. Hosts in us_east got the us_east value, but hosts in us_west got the webservers value because us_west didn't define it. The fix was to define region-specific variables only on region groups and tier-specific variables only on tier groups, avoiding overlap.
Key Takeaway: Design group hierarchy to minimize variable conflicts. Use group_vars/all for truly global defaults, and override only at the most specific level needed.
ansible-inventory -i inventory --graph to visualize the group hierarchy. This helps debug unexpected group membership.dc1 and dc2 as children of all. A host was moved from dc1 to dc2 but the old group_vars file for dc1 was not cleaned up. The host still picked up variables from dc1 because it was still listed in that group. The fix was to remove the host from the old group in the inventory file.ansible-inventory --graph to verify group membership after any inventory change.host_vars and group_vars Directories
Ansible automatically loads variables from host_vars/ and group_vars/ directories located relative to the inventory file or playbook directory. These directories contain YAML files named after the host or group.
Directory Structure Example: `` production/ inventory.yaml host_vars/ web1.example.com.yaml db1.example.com.yaml group_vars/ all.yaml webservers.yaml dbservers.yaml us_east.yaml ``
File Naming: The file name must match the hostname (for host_vars) or group name (for group_vars) exactly, including domain suffix. For example, web1.example.com.yaml for host web1.example.com.
Variable Loading Order: For a given host, Ansible loads variables in this order: 1. group_vars/all 2. group_vars of parent groups (alphabetically) 3. group_vars of the host's immediate groups (alphabetically) 4. host_vars/<hostname>
Later files override earlier ones. This means host_vars always wins over any group_vars, and within group_vars, the last group alphabetically wins.
Production Insight: We once had a variable app_port defined in group_vars/all.yaml as 8080, in group_vars/webservers.yaml as 80, and in host_vars/web1.example.com.yaml as 3000. The host web1.example.com got 3000, as expected. But another host web2.example.com (no host_vars) got 80, which was correct. However, we also had a group_vars/production.yaml that defined app_port: 9090. Because production was a parent group of webservers, and production came alphabetically after webservers, it actually overrode webservers? No, the loading order is parent groups first, then immediate groups. Since production is a parent, its variables are loaded before webservers, so webservers wins. This is a common point of confusion.
Key Takeaway: To avoid confusion, use group_vars/all for defaults, and override in specific group_vars or host_vars. Avoid defining the same variable in multiple group_vars at the same level.
ansible_host IP. The IP was defined in group_vars/all but overridden in group_vars/datacenter_a. However, the host belonged to both datacenter_a and datacenter_b groups. Because datacenter_b came alphabetically after datacenter_a, its group_vars loaded last and overrode the IP. The fix was to ensure only one group defined that variable for the host.