Ansible Host Groups — Organize Inventory for Targeted Automation
Introduction
Host groups are how you organize servers in Ansible inventory. Instead of listing individual hosts in every playbook, you group them by function (webservers, databases), environment (production, staging), location (us-east, eu-west), or any criteria that makes sense for your infrastructure. This guide covers group structure, nesting, variables, and targeting patterns.
Basic Group Structure
INI Format
# inventory.ini
[webservers]
web01.example.com
web02.example.com
web03.example.com
[databases]
db01.example.com
db02.example.com
[loadbalancers]
lb01.example.com
[monitoring]
mon01.example.com
YAML Format
# inventory.yml
all:
children:
webservers:
hosts:
web01.example.com:
web02.example.com:
web03.example.com:
databases:
hosts:
db01.example.com:
db02.example.com:
loadbalancers:
hosts:
lb01.example.com:
monitoring:
hosts:
mon01.example.com:
Nested Groups (Children)
# inventory.yml
all:
children:
# Environment groups
production:
children:
prod_web:
hosts:
web01.example.com:
web02.example.com:
prod_db:
hosts:
db01.example.com:
staging:
children:
stage_web:
hosts:
web-stage01.example.com:
stage_db:
hosts:
db-stage01.example.com:
# Function groups (same hosts, different grouping)
webservers:
children:
prod_web:
stage_web:
databases:
children:
prod_db:
stage_db:
A host can belong to multiple groups:
web01.example.com is in:
- prod_web
- production (parent of prod_web)
- webservers (parent of prod_web)
- all (always)
Group Variables
Directory Structure
project/
├── inventory.yml
├── group_vars/
│ ├── all.yml # Applied to every host
│ ├── webservers.yml # Applied to webservers group
│ ├── databases.yml # Applied to databases group
│ ├── production.yml # Applied to production group
│ └── staging.yml # Applied to staging group
├── host_vars/
│ ├── web01.example.com.yml
│ └── db01.example.com.yml
└── playbooks/
└── site.yml
Variable Files
# group_vars/all.yml
---
ntp_servers:
- ntp1.example.com
- ntp2.example.com
dns_servers:
- 10.0.0.53
- 10.0.0.54
admin_email: ops@example.com
# group_vars/webservers.yml
---
nginx_worker_processes: auto
nginx_worker_connections: 4096
app_port: 8080
ssl_certificate: /etc/ssl/certs/web.pem
# group_vars/databases.yml
---
postgresql_version: 16
postgresql_max_connections: 500
backup_schedule: "0 2 * * *"
backup_retention_days: 30
# group_vars/production.yml
---
env_name: production
log_level: warn
monitoring_enabled: true
backup_enabled: true
# group_vars/staging.yml
---
env_name: staging
log_level: debug
monitoring_enabled: false
backup_enabled: false
Variable Precedence with Groups
Priority (low → high):
1. all group vars
2. parent group vars
3. child group vars
4. host_vars
5. play vars
6. extra vars (-e)
Example for web01 in prod_web → production → webservers:
all.yml → ntp_servers, dns_servers
production.yml → env_name=production, log_level=warn
webservers.yml → nginx_worker_processes, app_port
web01.yml → override anything for this specific host
Targeting Groups in Playbooks
# Run on specific group
- name: Configure web servers
hosts: webservers
tasks:
- name: Install Nginx
ansible.builtin.package:
name: nginx
state: present
# Run on multiple groups
- name: Update all servers
hosts: webservers:databases:loadbalancers
tasks:
- name: Update packages
ansible.builtin.package:
name: "*"
state: latest
# Run on intersection (hosts in BOTH groups)
- name: Production web servers only
hosts: webservers:&production
tasks:
- name: Deploy app
ansible.builtin.copy:
src: app.tar.gz
dest: /opt/app/
# Exclude a group
- name: All except staging
hosts: all:!staging
tasks:
- name: Production task
ansible.builtin.debug:
msg: "Not staging"
Command Line Targeting
# Target specific group
ansible webservers -m ping
ansible-playbook site.yml --limit webservers
# Multiple groups
ansible 'webservers:databases' -m ping
# Intersection
ansible 'webservers:&production' -m ping
# Exclusion
ansible 'all:!staging' -m ping
# Regex pattern
ansible '~web\d+' -m ping
# Specific hosts within a group
ansible 'webservers[0]' -m ping # First host
ansible 'webservers[0:2]' -m ping # First three hosts
Built-in Groups
Ansible provides two groups automatically:
# 'all' — every host in inventory
- hosts: all
# 'ungrouped' — hosts not in any group (except 'all')
- hosts: ungrouped
Multi-Environment Inventory
Separate Files
inventories/
├── production/
│ ├── hosts.yml
│ └── group_vars/
│ ├── all.yml
│ └── webservers.yml
├── staging/
│ ├── hosts.yml
│ └── group_vars/
│ ├── all.yml
│ └── webservers.yml
└── development/
├── hosts.yml
└── group_vars/
└── all.yml
# Target specific environment
ansible-playbook site.yml -i inventories/production/
ansible-playbook site.yml -i inventories/staging/
Dynamic Group Membership
# Add hosts to groups dynamically
- name: Classify hosts
hosts: all
tasks:
- name: Add to docker_hosts group
ansible.builtin.group_by:
key: "docker_hosts"
when: "'docker' in ansible_facts.packages"
- name: Group by OS
ansible.builtin.group_by:
key: "os_{{ ansible_distribution | lower }}"
- name: Configure Docker hosts
hosts: docker_hosts
tasks:
- name: Docker-specific config
ansible.builtin.debug:
msg: "This host has Docker installed"
Troubleshooting
| Issue | Solution |
|---|---|
| "No hosts matched" | Check group name spelling; run ansible-inventory --list |
| Variables not applied | Check group_vars/ directory name matches group name exactly |
| Wrong variable value | Check precedence: child group vars override parent |
| Host in wrong group | Run ansible-inventory --graph to visualize |
group_vars not loaded | Directory must be next to inventory file or in project root |
# Debug inventory structure
ansible-inventory -i inventory.yml --graph
ansible-inventory -i inventory.yml --list
ansible-inventory -i inventory.yml --host web01.example.com
Best Practices
- Group by function AND environment — use nested groups so hosts belong to both
- Use
group_vars/all.ymlfor truly global settings — NTP, DNS, admin contacts - Keep host_vars minimal — most config should come from groups
- Separate inventories per environment —
inventories/production/,inventories/staging/ - Name groups descriptively —
prod_webserversnotgroup1 - Use
ansible-inventory --graph— visualize before running playbooks
Conclusion
Host groups are the foundation of organized Ansible automation. By grouping servers by function and environment, you write playbooks once and target them precisely. Combined with group_vars, each group gets its own configuration without duplicating variables across hosts. Start simple (one group per function), add environment groups as you grow, and use nested groups to combine both dimensions.