group_vars and host_vars: Where Settings Live
Scenario: Staging values leaked into production. The cause was a variable in group_vars/all.yml that someone assumed only applied to staging.
New words, in plain English
- Variable - a named value your automation can use - a port number, a package name, a file path.
- group_vars/ - a directory of files named after groups.
group_vars/webservers.ymlapplies to every host inwebservers. - host_vars/ - the same idea for a single machine.
host_vars/web01.ymlapplies only to web01. - Role defaults - the lowest-priority values a reusable role ships with, so it works out of the box.
Hard-coding values inside tasks makes automation unreusable. Variables move those values out into files organised by who they apply to, which is the whole design.
The conventional layering is worth learning as a rule of thumb and then internalising:
- Role defaults - sensible starting values so a role runs with no configuration at all.
group_vars/all.yml- genuine estate-wide facts. Be sparing: this file reaches everything.group_vars/<group>.yml- the normal home for environment or tier settings.host_vars/<host>.yml- genuine per-machine exceptions only.-eextra vars on the command line - emergencies. They beat everything.
Ansible also supports a directory instead of a file: group_vars/dbservers/ containing vars.yml and vault.yml is loaded as one merged set. That pattern matters enormously later, because it lets you keep secrets encrypted in vault.yml while ordinary settings stay readable in vars.yml.
Analogy: Company policy applies to everyone. A department can set its own stricter rule. An individual can have a documented exception. And the CEO can override anything in an emergency - which is fine occasionally, and a disaster as a routine.
A worked example
# group_vars/all.yml - true for the whole estate
ntp_server: time.internal
app_env: production
# group_vars/webservers.yml - true for the web tier
app_port: 8081
worker_count: 4
# host_vars/lb01.yml - a genuine, documented exception
# lb01 fronts a legacy partner integration on a fixed port.
app_port: 8088
# group_vars/dbservers/ (a DIRECTORY, loaded as one set)
# vars.yml -> db_user: checkout_app
# vault.yml -> encrypted secrets
Ansible looks for group_vars/ and host_vars/ in two places: next to the playbook, and next to the inventory. Inventory-adjacent is the better default for anything that describes the estate, because tools like ansible-inventory --host web01 can then resolve it too. Playbook-adjacent is fine for values that only make sense to that playbook.
The practical debugging move when a value is wrong: do not read the files hunting for it. Ask Ansible. ansible-inventory --host web01 prints every inventory-sourced variable for that host, and a debug task prints the fully-resolved value at the exact point the play would use it.
Tip: A variable that needed a comment explaining why it is inhost_varsis usually correct. A variable ingroup_vars/all.ymlthat needed a comment explaining who it does NOT apply to is usually in the wrong place.
Goal: Put this to work in the ansible-variable-precedence lab. Open/labs/ansible, pickansible-variable-precedence, and fix the real broken project - Ansible really does SSH into four managed hosts and converge them.