unit 5 · Logic
Facts, set_fact & caching
What Ansible already knows about every host
you may want to read 15 · Registering output & debug first — but nothing is locked
tl;dr
- Facts = per-host system data (OS, IPs, memory), auto-gathered by the setup module into ansible_facts.
- gather_facts: false skips the survey; fact caching (jsonfile/redis) reuses results across runs.
- set_fact writes your own runtime facts; /etc/ansible/facts.d adds custom facts on the node itself.
why it matters Every real playbook branches on facts — without them you can’t read a role that installs differently per OS, or explain why each play burns seconds gathering data nothing uses.
Lesson 7 had you run one ad-hoc command that answered with a wall of JSON:
ansible all -m ansible.builtin.setup. Lesson 12 showed every play pausing
on a “Gathering Facts” step before its first task. Same machinery both
times. Before your tasks run, Ansible executes the setup module on each
host and surveys it — OS and version, network interfaces and IPs, memory,
disks, CPUs. Each answer is a fact . The whole survey
lands in one per-host dictionary, ansible_facts, readable in any later
task like any other variable:
- name: Identify this host
ansible.builtin.debug:
msg: >- # >- folds these lines into one string
{{ ansible_facts['distribution'] }}
{{ ansible_facts['memtotal_mb'] }} MB RAMOlder content reads the same data through bare top-level names like
ansible_distribution. Those still work, but ansible_facts['...'] is
the current documented style. It also makes it obvious you’re reading a
fact, not one of your own variables.
The switch, and what it costs
The survey isn’t free — it’s a real module execution per host, a few
seconds each, every play. gather_facts defaults to true; when a play
never reads a fact, turn it off:
- hosts: web
gather_facts: false # no setup run — faster, but ansible_facts stays emptyLesson 15 told you registered variables and facts are the only state that survives between tasks. Keep them straight: a registered variable captures what a task you ran returned. Facts describe the host itself, collected for you. Both are per-host, both last exactly one run.
Facts you set yourself
The third source is you. set_fact is a module that writes values into a host’s facts mid-play — compute something once, and every later task on that host can read it:
- name: Derive values other tasks will reuse
ansible.builtin.set_fact:
app_user: 'svc-web'
cache_dir: "/var/cache/{{ ansible_facts['hostname'] }}"One warning: set_fact sits high in lesson 16’s precedence table. A fact
you set silently beats the same name from group_vars — either exactly
what you wanted, or a confusing afternoon.
Caching: stop paying twice
Facts are gathered fresh every run unless you cache them. Fact caching
stores each host’s survey in a backend — jsonfile for a simple local
cache, redis for one shared between machines. gathering = smart skips
re-surveying hosts whose cache is still valid. It lives in lesson 6’s
file:
[defaults]
gathering = smart # skip hosts with valid cached facts
fact_caching = jsonfile # or redis for a shared cache
fact_caching_connection = ~/.ansible/factcache
fact_caching_timeout = 86400 # seconds until cached facts expireThe trade is speed for freshness. Re-IP a host and a stale cache keeps reporting the old address until the timeout. When a fact looks impossibly wrong, ask whether you’re reading a cache.
Two edges to file away. Custom facts: drop a *.fact file — INI or
JSON, or executable printing JSON — into /etc/ansible/facts.d/ on the
managed node. Its contents appear at the next gather under
ansible_facts['ansible_local']. That’s how a host declares its own
metadata (“I am an app server, my release ring is 2”) instead of the
inventory declaring it. Windows: Windows hosts produce
facts too, gathered through PowerShell rather than Python, with different
names in places — unit 8 covers what changes.
Facts tell you what each host is. The next move is acting on it —
install this package only on Debian-family hosts, skip that step below
4 GB of RAM. That’s when:, next lesson, and facts are what it
feeds on.
self-check — recall, not a test
1 A play has gather_facts: false, and a task reads ansible_facts["os_family"]. What happens and why?
2 Facts and registered variables both survive between tasks. What separates them?
3 Runs feel slow and every play opens with a long Gathering Facts step. Name two fixes.
lab (optional — never required)
Run ansible.builtin.setup ad-hoc against localhost, then find three facts in the output: distribution, default IPv4 address, total memory.
needs: The control node from lesson 5.