unit 5 · Logic

Facts, set_fact & caching

What Ansible already knows about every host

essential · 7 min · linux + windows

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 RAM
facts are just variables — read them anywhere

Older 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 empty
skip the survey when nothing reads facts

Lesson 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'] }}"
compute once, reuse for the rest of the run

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 expire
fact caching in ansible.cfg

The 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. 1 A play has gather_facts: false, and a task reads ansible_facts["os_family"]. What happens and why?

  2. 2 Facts and registered variables both survive between tasks. What separates them?

  3. 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.

next up 18 · Conditionals with when 6 min · unit 5