Ansible Inventory Groups — Organize Hosts for Targeted Automation
Introduction
Inventory groups let you target specific sets of hosts — run a playbook on all web servers, or just the production database tier. This guide covers static group definitions, nested groups, host patterns, group variables, and common organizational patterns for real infrastructure.
Basic Group Structure
# inventory/hosts
[webservers]
web01.example.com
web02.example.com
web03.example.com
[dbservers]
db01.example.com
db02.example.com
[loadbalancers]
lb01.example.com
YAML Format
# inventory/hosts.yml
all:
children:
webservers:
hosts:
web01.example.com:
web02.example.com:
web03.example.com:
dbservers:
hosts:
db01.example.com:
db02.example.com:
loadbalancers:
hosts:
lb01.example.com:
Nested Groups (Children)
# inventory/hosts
[webservers]
web01.example.com
web02.example.com
[dbservers]
db01.example.com
[monitoring]
grafana01.example.com
# Parent groups using :children
[production:children]
webservers
dbservers
loadbalancers
[staging:children]
staging_web
staging_db
[all_servers:children]
production
staging
monitoring
Environment-Based Organization
inventory/
├── production/
│ ├── hosts.yml
│ └── group_vars/
│ ├── all.yml
│ ├── webservers.yml
│ └── dbservers.yml
├── staging/
│ ├── hosts.yml
│ └── group_vars/
│ ├── all.yml
│ └── webservers.yml
└── development/
└── hosts.yml
# Target specific environment
ansible-playbook -i inventory/production site.yml
ansible-playbook -i inventory/staging site.yml
Host Patterns
# All hosts in a group
ansible webservers -m ping
# Multiple groups (OR)
ansible 'webservers:dbservers' -m ping
# Intersection (AND) — hosts in BOTH groups
ansible 'production:&webservers' -m ping
# Exclusion (NOT) — production except dbservers
ansible 'production:!dbservers' -m ping
# Wildcards
ansible 'web*' -m ping
ansible '*.example.com' -m ping
# Regex
ansible '~web[0-9]+\.example\.com' -m ping
# Numbered ranges
ansible 'web[01:50].example.com' -m ping
# Single host
ansible web01.example.com -m ping
# All hosts
ansible all -m ping
Group Variables
# group_vars/webservers.yml
http_port: 80
max_connections: 1000
document_root: /var/www/html
ssl_enabled: true
# group_vars/dbservers.yml
postgresql_port: 5432
max_connections: 200
shared_buffers: 4GB
# group_vars/all.yml (applies to every host)
ansible_user: deploy
ansible_python_interpreter: auto_silent
ntp_server: time.example.com
dns_servers:
- 10.0.0.53
- 10.0.1.53
Host Variables
# Per-host overrides
[webservers]
web01.example.com http_port=8080 max_connections=2000
web02.example.com
web03.example.com ansible_host=10.0.1.103
# host_vars/web01.example.com.yml
http_port: 8080
max_connections: 2000
ssl_certificate: /etc/ssl/web01.pem
Variable Precedence (Group Level)
1. host_vars/hostname.yml (highest)
2. group_vars/child_group.yml
3. group_vars/parent_group.yml
4. group_vars/all.yml (lowest)
Real-World Patterns
Functional Grouping
[webservers]
[appservers]
[dbservers]
[cacheservers]
[messagequeues]
Geographic Grouping
[us_east]
[us_west]
[eu_west]
[ap_southeast]
[north_america:children]
us_east
us_west
Combined Pattern
# Functional
[webservers]
web-us-east-01
web-us-west-01
web-eu-01
[dbservers]
db-us-east-01
db-eu-01
# Geographic
[us_east]
web-us-east-01
db-us-east-01
[eu_west]
web-eu-01
db-eu-01
# Environment
[production:children]
webservers
dbservers
Playbook Targeting
---
# Deploy to web servers only
- name: Deploy web application
hosts: webservers
tasks:
- name: Deploy code
ansible.builtin.git:
repo: https://github.com/org/webapp.git
dest: /var/www/app
---
# Database maintenance — production only
- name: Database backup
hosts: production:&dbservers
tasks:
- name: Dump database
community.postgresql.postgresql_db:
name: app_db
state: dump
target: /backups/app_db.sql
---
# Rolling update — exclude load balancers
- name: System updates
hosts: production:!loadbalancers
serial: "25%"
tasks:
- name: Update packages
ansible.builtin.apt:
upgrade: safe
Magic Groups
Ansible automatically creates two groups:
# "all" — every host in inventory
# "ungrouped" — hosts not in any explicit group
[ungrouped]
standalone-server.example.com # Only in "all" and "ungrouped"
List and Debug Groups
# Show all groups and hosts
ansible-inventory --list
# Show graph (tree view)
ansible-inventory --graph
# @all:
# |--@webservers:
# | |--web01.example.com
# | |--web02.example.com
# |--@dbservers:
# | |--db01.example.com
# Show specific host's variables
ansible-inventory --host web01.example.com
# List hosts matching a pattern
ansible 'production:&webservers' --list-hosts
Troubleshooting
| Issue | Solution |
|---|---|
| "No hosts matched" | Check group name spelling, verify with --list-hosts |
| Variable not applying | Check precedence — host_vars > child group_vars > parent group_vars |
| Host in wrong group | Run ansible-inventory --graph to visualize |
| Duplicate host warnings | Same host listed in multiple places — use :children instead |
| Group vars file ignored | Filename must match group name exactly |
Best Practices
- Use both functional and environment groups — target "production web servers" precisely
- Keep
group_vars/all.ymlminimal — only truly global settings - One host_vars file per special host — don't clutter inventory file
- Use YAML inventory for complex setups — more readable than INI
- Separate inventory per environment —
inventory/production/,inventory/staging/ - Document group purpose — comments in inventory file
Conclusion
Organize inventory into functional groups (webservers, dbservers) and environmental groups (production, staging). Use nested groups with :children for hierarchical targeting. Apply variables at the right level — group_vars/all.yml for global, group_vars/webservers.yml for role-specific, host_vars/ for individual overrides. Debug with ansible-inventory --graph and target precisely with patterns like production:&webservers:!maintenance.