What is the set_fact Module?
The ansible.builtin.set_fact module lets you create or modify variables dynamically during playbook execution. Unlike variables defined in vars, group_vars, or host_vars, facts set with set_fact are computed at runtime and can depend on the results of previous tasks.
This makes set_fact essential for:
- Transforming data from API responses or command output
- Building conditional variables based on host properties
- Creating computed values from multiple sources
- Setting host-specific facts that persist across plays (with
cacheable: true)
Basic Syntax
The simplest use of set_fact creates a variable with a static or computed value:
- name: Set a simple fact
ansible.builtin.set_fact:
my_environment: production
- name: Set a fact from another variable
ansible.builtin.set_fact:
config_path: "/etc/{{ app_name }}/config.yml"
You can set multiple facts in a single task:
- name: Set multiple facts
ansible.builtin.set_fact:
app_port: 8080
app_host: "{{ ansible_default_ipv4.address }}"
app_url: "http://{{ ansible_default_ipv4.address }}:8080"
set_fact vs register: When to Use Each
A common source of confusion is when to use set_fact versus register. Here's the distinction:
register captures the entire output of a task:
- name: Get disk usage
ansible.builtin.command: df -h /
register: disk_output
# disk_output.stdout contains the full output
set_fact creates a variable with a specific, often transformed, value:
- name: Get disk usage
ansible.builtin.command: df -h / --output=pcent
register: disk_output
- name: Extract disk usage percentage
ansible.builtin.set_fact:
disk_usage_percent: "{{ disk_output.stdout_lines[1] | trim | replace('%', '') | int }}"
Use register when you need the raw output of a task. Use set_fact when you need a clean, transformed value.
Practical Examples
Conditional Fact Setting
Set different values based on the operating system:
- name: Set package manager based on OS
ansible.builtin.set_fact:
pkg_manager: "{{ 'apt' if ansible_os_family == 'Debian' else 'yum' }}"
service_manager: "{{ 'systemd' if ansible_service_mgr == 'systemd' else 'sysvinit' }}"
Building Dynamic Data Structures
Create lists and dictionaries dynamically:
- name: Build server configuration
ansible.builtin.set_fact:
server_config:
hostname: "{{ inventory_hostname }}"
ip: "{{ ansible_default_ipv4.address }}"
environment: "{{ env | default('staging') }}"
services:
- nginx
- postgresql
- redis
Extracting Data from API Responses
A common pattern is querying an API and storing specific fields:
- name: Get cluster info
ansible.builtin.uri:
url: "https://api.example.com/clusters/{{ cluster_name }}"
headers:
Authorization: "Bearer {{ api_token }}"
register: cluster_response
- name: Extract cluster details
ansible.builtin.set_fact:
cluster_endpoint: "{{ cluster_response.json.endpoint }}"
cluster_version: "{{ cluster_response.json.version }}"
cluster_nodes: "{{ cluster_response.json.nodes | length }}"
Accumulating Facts in a Loop
Build a list by appending items in a loop:
- name: Gather service ports
ansible.builtin.set_fact:
active_ports: "{{ active_ports | default([]) + [item.port] }}"
loop:
- { name: 'web', port: 80 }
- { name: 'api', port: 8080 }
- { name: 'db', port: 5432 }
when: item.name in enabled_services
Using set_fact with Filters
Combine set_fact with Jinja2 filters for data transformation:
- name: Get available packages
ansible.builtin.uri:
url: "{{ centos_repo }}"
return_content: true
register: available_packages
- name: Extract kernel packages
ansible.builtin.set_fact:
kernel: "{{ available_packages.content | regex_replace('<.*?>') | regex_findall('kernel-[0-9].*rpm') }}"
- name: Display found packages
ansible.builtin.debug:
var: kernel
Date and Time Facts
Set facts using the current date and time:
- name: Set timestamp facts
ansible.builtin.set_fact:
backup_date: "{{ lookup('pipe', 'date +%Y-%m-%d') }}"
backup_timestamp: "{{ lookup('pipe', 'date +%Y-%m-%d-%H%M%S') }}"
backup_dir: "/backups/{{ lookup('pipe', 'date +%Y-%m-%d') }}"
Cacheable Facts
By default, facts set with set_fact only persist for the current playbook run. To make them survive across runs, use cacheable: true:
- name: Set a cacheable fact
ansible.builtin.set_fact:
last_deployment_time: "{{ ansible_date_time.iso8601 }}"
cacheable: true
This requires a fact caching backend to be configured in ansible.cfg:
[defaults]
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts_cache
fact_caching_timeout = 86400
Variable Precedence
Facts set with set_fact have high precedence in Ansible's variable hierarchy. They override:
- Inventory variables (
host_vars,group_vars) - Variables from
varssections - Variables from
vars_files - Role defaults
Only extra_vars (-e on command line) take higher precedence than set_fact.
Common Mistakes
Forgetting Jinja2 Quotes
# Wrong — interpreted as YAML, not a Jinja2 expression
- ansible.builtin.set_fact:
my_list: [1, 2, 3]
# Correct — explicit Jinja2 expression
- ansible.builtin.set_fact:
my_list: "{{ [1, 2, 3] }}"
Type Handling
Variables set with set_fact retain their Python type when using Jinja2 expressions:
- ansible.builtin.set_fact:
count_string: "42" # String
count_int: "{{ 42 }}" # Integer
is_enabled: "{{ true }}" # Boolean
Undefined Variable Errors
Always provide defaults when a variable might not exist:
- ansible.builtin.set_fact:
app_port: "{{ custom_port | default(8080) }}"
app_debug: "{{ debug_mode | default(false) }}"
Performance Considerations
set_factruns on each host individually — in a play with 100 hosts, the task executes 100 times- Use
run_once: truewhen the fact is the same for all hosts to reduce execution time - For large data structures, consider using
varsorinclude_varsinstead ofset_fact
- name: Set a fact once for all hosts
ansible.builtin.set_fact:
shared_config_version: "{{ lookup('file', 'VERSION') }}"
run_once: true
Related Articles
- The run_once Statement in Ansible
- Ansible Debug Module Guide
- Automating Dynamic Time Date Facts with Ansible
- Ansible Magic Variables Reference
- Ansible Best Practices Guide
- Ansible Jinja2 Ternary Filter
- Ansible Error Handling Guide
Conclusion
The ansible.builtin.set_fact module is one of Ansible's most versatile tools for dynamic playbook logic. It bridges the gap between static configuration and runtime computation, enabling playbooks that adapt to their environment.
Key takeaways:
- Use
set_factfor computed, transformed, or conditional values - Use
registerfor raw task output, thenset_factto extract what you need - Enable
cacheable: truewhen facts should persist across runs - Combine with
run_oncefor facts that are identical across all hosts - Always provide sensible defaults with the
default()filter