Variables and Facts
Scope: how one playbook configures different machines differently — values you set (variables) and values Ansible discovers (facts) — plus where to put them without making a mess.
Variables you set
You've already met three kinds:
- Inventory group vars — in
examples/inventory.ini:[webservers:vars] app_port=8080. Every webserver gets it. - Inventory host vars — after a hostname on its own
line:
web1 app_port=9090would override the group for web1 alone. - Playbook
vars:— the play-level block insite.yml(app_dir). Good for values intrinsic to the playbook itself.
And one built-in, magic variables: inventory_hostname
(the host's inventory name), groups (all groups and
their members), hostvars (another host's variables —
how a load balancer learns the web servers' addresses).
Where files beat inventory lines
Group/host vars outgrow INI files fast. The grown-up layout keeps them in YAML files Ansible finds by name:
group_vars/webservers.yml # applies to the group
host_vars/web1.yml # applies to one host
# group_vars/webservers.yml
app_port: 8080
env_name: production
Same effect as the INI block, but with room for lists, nested data, and comments. The examples use the INI form so everything fits on one screen; your real project should use the files.
Facts: variables Ansible discovers
At the start of every play, Ansible runs Gathering Facts
(the task you saw in the playbook output) — the setup
module interrogating each machine. Results land in
ansible_facts:
"ansible_facts": {
"ansible_distribution": "Ubuntu",
"ansible_distribution_version": "24.04",
...
}
Use them anywhere a variable fits:
- name: Say where I am
ansible.builtin.debug:
msg: "{{ inventory_hostname }} runs {{ ansible_facts['distribution'] }} {{ ansible_facts['distribution_version'] }}"
Facts are how a single playbook installs with apt on
Debian-family machines and dnf on RedHat-family ones,
sizes a buffer from installed RAM, or skips a task on
the one ancient box. Two practical notes:
- Facts cost time (a second or two per host). Playbooks
that don't need them can say
gather_facts: false— the demo handler in this guide uses a fact (date_time), which is whysite.ymlleaves gathering on. ansible <host> -m setupdumps everything a machine offers. Grep it before assuming a fact's name.
Precedence, in plain terms
Ansible has a full variable-precedence ladder (20+ levels). The mortal version:
- Command line (
-e extra=vars) beats everything. - Host vars beat group vars; a more specific group beats a broader one.
- Playbook
vars:beat inventory defaults; role defaults lose to nearly everything (by design — they're defaults).
When a value surprises you, it is almost always an
override you forgot existed. ansible-inventory --host
web1 prints a host's fully-merged variables — check
there before suspecting Ansible of creativity.