Understanding Ansible Facts
Last updated: June 29, 2025
My Experience with Ansible Facts
When I first started working with Ansible, one of the most fascinating features I discovered was Ansible facts. These automatically gathered pieces of information about your managed systems are like gold for automation tasks. In my early days of automating mixed Linux and Windows environments, I often found myself manually gathering system information before writing playbooks. Once I understood the power of Ansible facts, my approach completely changed.
In this blog post, I'll share my experience with Ansible facts, including practical examples for both Linux and Windows environments, how to work with them effectively, and how to extend them with custom facts when needed.
What Are Ansible Facts?
Ansible facts are pieces of information about remote systems that Ansible automatically discovers when running playbooks. These facts contain details about the operating system, hardware, network interfaces, and much more. Think of facts as a comprehensive system inventory that's automatically collected and made available to you.
Ansible uses the setup
module to gather these facts. By default, fact gathering happens automatically at the beginning of each play, making this information available to all your tasks.
Viewing Ansible Facts
My favorite way to understand what facts are available is to simply display them. You can view all facts for a host using this simple ad-hoc command:
# View all facts for a specific host
ansible hostname -m setup
# Save facts to a file for easier review
ansible hostname -m setup > facts.json
For Windows hosts, the same command works:
# View all facts for a Windows host
ansible windows_host -m setup
Core Facts Structure
Ansible facts are structured as nested JSON data. Here's a simplified view of what the structure looks like:
{
"ansible_facts": {
"ansible_architecture": "x86_64",
"ansible_distribution": "Ubuntu",
"ansible_distribution_version": "20.04",
"ansible_fqdn": "webserver.example.com",
"ansible_hostname": "webserver",
"ansible_interfaces": ["lo", "eth0", "eth1"],
"ansible_memory_mb": {
"real": {
"free": 7254,
"total": 16024,
"used": 8770
}
},
"ansible_processor_cores": 8,
"ansible_processor_count": 1
}
}
Accessing Facts in Playbooks
There are two main ways to access facts in your playbooks and templates:
Using the
ansible_facts
dictionary:- name: Show system information debug: msg: "This is a {{ ansible_facts['distribution'] }} {{ ansible_facts['distribution_version'] }} system"
Using top-level variables with the
ansible_
prefix (though this is becoming deprecated):- name: Show system information debug: msg: "This is a {{ ansible_distribution }} {{ ansible_distribution_version }} system"
I personally prefer the first approach as it's more explicit and future-proof.
Useful Facts for Linux Systems
After working with Ansible for several years, I've found these Linux facts to be particularly useful:
- name: Display useful Linux facts
debug:
msg:
- "OS: {{ ansible_facts['distribution'] }} {{ ansible_facts['distribution_version'] }}"
- "Kernel: {{ ansible_facts['kernel'] }}"
- "Hostname: {{ ansible_facts['hostname'] }}"
- "FQDN: {{ ansible_facts['fqdn'] }}"
- "Main IP: {{ ansible_facts['default_ipv4']['address'] }}"
- "Total Memory: {{ ansible_facts['memtotal_mb'] }} MB"
- "Processor Cores: {{ ansible_facts['processor_cores'] }}"
- "Processor Count: {{ ansible_facts['processor_count'] }}"
when: ansible_facts['os_family'] == 'Debian' or ansible_facts['os_family'] == 'RedHat'
Useful Facts for Windows Systems
Windows facts differ somewhat from Linux facts. Here are some Windows-specific facts I commonly use:
- name: Display useful Windows facts
debug:
msg:
- "OS: {{ ansible_facts['os_name'] }} {{ ansible_facts['os_version'] }}"
- "Hostname: {{ ansible_facts['hostname'] }}"
- "FQDN: {{ ansible_facts['fqdn'] }}"
- "Main IP: {{ ansible_facts['ip_addresses'][0] }}"
- "Windows Domain: {{ ansible_facts['domain'] }}"
- "Total Memory: {{ ansible_facts['memtotal_mb'] }} MB"
- "Processor Cores: {{ ansible_facts['processor_cores'] }}"
- "PowerShell Version: {{ ansible_facts['powershell_version'] }}"
when: ansible_facts['os_family'] == 'Windows'
Filtering Facts
The full facts output can be overwhelming. In my experience, it's better to filter facts to get just what you need:
# Filter facts by a specific pattern
ansible hostname -m setup -a "filter=ansible_*memory*"
# Get only network-related facts
ansible hostname -m setup -a "gather_subset=network"
# Filter Windows-specific facts
ansible windows_host -m setup -a "filter=ansible_win*"
For complex filtering, I sometimes use regular expressions:
# Find all mount points using grep
ansible hostname -m setup | grep -i 'mount'
# Find memory-related facts
ansible hostname -m setup -a "filter=ansible_mem*"
Facts Caching
One optimization I've found essential in large environments is fact caching. By default, Ansible gathers facts each time a playbook runs, which can be time-consuming for large inventories.
To enable fact caching, I add these lines to my ansible.cfg
:
[defaults]
# Cache facts to JSON files for 24 hours
fact_caching = jsonfile
fact_caching_connection = /path/to/facts/cache
fact_caching_timeout = 86400
This dramatically speeds up repeated playbook runs, especially in environments with hundreds of hosts.
Custom Facts
Sometimes the built-in facts aren't enough. That's where custom facts come in. There are two main ways to create custom facts:
1. Using set_fact
For temporary facts within a playbook, I use set_fact
:
- name: Create custom facts
set_fact:
app_environment: production
app_version: 1.2.3
deploy_date: "{{ lookup('pipe', 'date +%Y-%m-%d') }}"
2. Using facts.d (Permanent Custom Facts)
For permanent custom facts, I create files in the /etc/ansible/facts.d
directory on managed hosts. These can be INI, JSON, or executable scripts that output JSON.
Here's a simple example of a custom fact file I've used for an application inventory:
- name: Create facts.d directory
file:
path: /etc/ansible/facts.d
state: directory
mode: 0755
become: true
- name: Create custom fact file
copy:
content: |
[application]
name=inventory-service
version=2.1.0
environment=production
owner=operations
dest: /etc/ansible/facts.d/application.fact
mode: 0644
become: true
- name: Re-gather facts to include custom facts
setup:
filter: ansible_local
I can then access these facts using ansible_facts['ansible_local']
:
- name: Show custom facts
debug:
msg: "App {{ ansible_facts['ansible_local']['application']['application']['name'] }} version {{ ansible_facts['ansible_local']['application']['application']['version'] }}"
For Windows, the process is similar, but the facts.d directory is located at C:\ProgramData\Ansible\facts.d
:
- name: Create facts.d directory on Windows
win_file:
path: C:\ProgramData\Ansible\facts.d
state: directory
when: ansible_facts['os_family'] == 'Windows'
- name: Create custom fact file on Windows
win_copy:
content: |
{
"application": {
"name": "windows-service",
"version": "3.0.5",
"environment": "production",
"owner": "windows-team"
}
}
dest: C:\ProgramData\Ansible\facts.d\application.fact
when: ansible_facts['os_family'] == 'Windows'
A Practical Facts Use Case: System-Specific Configuration
One of my favorite patterns is to use facts for dynamic configuration. Here's a real-world example I've used for configuring services based on system resources:
---
- name: Configure applications based on system facts
hosts: all
tasks:
- name: Set memory allocation based on total memory
set_fact:
app_memory: "{{ (ansible_facts['memtotal_mb'] * 0.6) | int }}m"
app_threads: "{{ ansible_facts['processor_cores'] * 2 }}"
- name: Generate Linux configuration
template:
src: app.conf.j2
dest: /etc/app/config.conf
vars:
max_memory: "{{ app_memory }}"
thread_count: "{{ app_threads }}"
when: ansible_facts['os_family'] != 'Windows'
- name: Generate Windows configuration
win_template:
src: app.conf.j2
dest: C:\Program Files\App\config.conf
vars:
max_memory: "{{ app_memory }}"
thread_count: "{{ app_threads }}"
when: ansible_facts['os_family'] == 'Windows'
This ensures the application is configured optimally for each system's resources.
Sequence Diagram: Ansible Facts Collection Flow
Here's a visualization of how Ansible facts are gathered and used:
Performance Considerations
In large environments, fact gathering can become a performance bottleneck. Here are some strategies I've used to address this:
Disable fact gathering when not needed:
- hosts: all gather_facts: no tasks: - ping:
Use fact caching as mentioned earlier.
Gather only specific fact subsets:
- hosts: all gather_facts: yes gather_subset: - '!all' - 'network' - 'hardware'
Create host groups based on facts to streamline operations:
- name: Create host groups based on OS hosts: all tasks: - name: Create OS-specific groups group_by: key: "os_{{ ansible_facts['distribution'] | lower }}"
Conclusion
Ansible facts have been a cornerstone of my automation strategy. They provide the dynamic information needed to make playbooks adaptable to different environments and system configurations. Whether it's basic system information or complex custom data, facts provide a structured way to access this information.
In my experience, understanding and effectively using facts is what separates basic Ansible scripts from truly robust automation solutions. Once you master facts, your playbooks become more dynamic, more adaptable, and ultimately more valuable.
As I continue to automate increasingly complex environments, I find myself relying on facts more and more to make intelligent decisions within my playbooks. I hope sharing my experiences helps you leverage the full power of Ansible facts in your automation journey.
Last updated