Facts & Variable Precedence¶
Variables covered where you define values. This page covers the variables Ansible provides automatically (facts and magic variables) and the rules for which value wins when the same name is defined in more than one place.
Facts — what Ansible discovers about a host¶
Before running tasks, Ansible connects to each host and gathers facts: OS,
IP addresses, CPU, memory, disks, and more. They live in the ansible_facts
dictionary, available in tasks and templates.
Why ansible_facts['...'] and not ansible_distribution_release
Older examples use top-level variables like ansible_distribution_release. They
still work, but since ansible-core 2.20 that form prints a deprecation warning
(INJECT_FACTS_AS_VARS will default to False in 2.24), so every example in this
section uses the dictionary form: ansible_facts['distribution_release'].
The setup module's filter= still matches the ansible_-prefixed names, as below.
# See every fact for a host
ansible server1.example.com -m setup
# Filter to a subset
ansible server1.example.com -m setup -a "filter=ansible_distribution*"
ansible server1.example.com -m setup -a "filter=ansible_memtotal_mb"
Facts you'll reach for often:
| Fact | Example value |
|---|---|
ansible_facts['distribution'] |
Debian |
ansible_facts['distribution_major_version'] |
13 |
ansible_facts['distribution_release'] |
trixie |
ansible_facts['os_family'] |
Debian |
ansible_facts['default_ipv4']['address'] |
192.0.2.10 |
ansible_facts['hostname'] |
server1 |
ansible_facts['processor_vcpus'] |
4 |
ansible_facts['memtotal_mb'] |
7900 |
They're used throughout this repo — e.g. the Docker role builds the apt repo line
with {{ ansible_facts['distribution_release'] }} so the same task works on Debian 12 or 13.
Turn off fact-gathering to speed up simple plays
Gathering facts adds a round-trip per host. If a play doesn't need any facts, skip it:
Magic variables — always available¶
These describe the run itself, not the machine:
| Variable | Meaning |
|---|---|
inventory_hostname |
The host's name as written in the inventory (server1.example.com) |
inventory_hostname_short |
Just the first label (server1) — used for borg repo paths here |
group_names |
List of groups the current host belongs to |
groups |
Dict of every group → its hosts (groups['apps']) |
hostvars |
Access another host's variables: hostvars['nas.example.com'].borg_repo_path |
ansible_play_hosts |
Hosts still active in the current play |
- name: Show which groups this host is in
ansible.builtin.debug:
msg: "{{ inventory_hostname }} is in {{ group_names }}"
- name: Reference every app host
ansible.builtin.debug:
msg: "app servers: {{ groups['apps'] }}"
Setting variables at runtime — set_fact¶
Create or compute a variable mid-play:
- name: Compute half the RAM for a container limit
ansible.builtin.set_fact:
container_mem_mb: "{{ (ansible_facts['memtotal_mb'] | int * 0.5) | int }}"
- name: Use it later
ansible.builtin.debug:
msg: "Limiting to {{ container_mem_mb }} MB"
register (from Writing Tasks) is
the other runtime source — it captures a task's result.
Variable precedence (which value wins)¶
When the same variable name is defined in multiple places, Ansible picks by a fixed priority. From lowest to highest (higher wins), the parts you'll actually touch:
- Role defaults (
roles/x/defaults/main.yml) — the weakest, meant to be overridden group_vars/allgroup_vars/<specific group>host_vars/<host>- Facts /
set_fact/ registered vars -e/--extra-varson the command line — always wins
graph LR
A[role defaults] --> B[group_vars/all]
B --> C[group_vars/group]
C --> D[host_vars/host]
D --> E[set_fact / register]
E --> F["--extra-vars (-e)"]
Practical takeaways:
- Put safe fallbacks in
roles/<role>/defaults/main.yml— anything can override them. - Put shared settings in
group_vars/all.yml. - Host-specific overrides go in
host_vars/<host>.yml— they beat group vars (that's whyhost_vars/nas.example.com.ymlcan overridedocker_compose_dir). - Force a one-off value with
-e:
The full precedence list is longer
Ansible actually defines ~22 precedence levels. The six above cover everyday homelab use; see the reference if you hit a surprising override.