Ansible set_fact Module: Set Variables Dynamically (Complete Guide 2026)
By Luca Berton · Published 2024-01-01 · Category: installation
Master Ansible set_fact for dynamic variable assignment. Cacheable facts, conditional variables, loops, registered output, and real-world playbook examples.
The ansible.builtin.set_fact module lets you create or modify variables dynamically during playbook execution. Unlike static variables defined in vars, group_vars, or host_vars, facts set with set_fact are computed at runtime based on gathered data, registered outputs, or conditional logic. This makes it indispensable for complex automation workflows.
Understanding set_fact
The set_fact module creates host-level variables (facts) that persist for the remainder of the play. These facts can be based on registered outputs, computed values, or conditional expressions, making your playbooks dynamic and responsive to the actual state of your infrastructure.
Key Characteristics
• Creates variables at runtime (not static) • Facts are host-scoped (each host has its own copy) • Persists for the entire play (not just the current task) • Supports Jinja2 expressions and filters • Can be cached across playbook runs withcacheable: true
• Higher precedence than vars files and inventory variables
See also: ansible_date_time: Access Date, Time & Timestamp Facts in Ansible
Basic Syntax
---
- name: Demonstrate set_fact basics
hosts: all
tasks:
- name: Set a simple fact
ansible.builtin.set_fact:
app_env: production
app_port: 8080
- name: Display the facts
ansible.builtin.debug:
msg: "Environment: {{ app_env }}, Port: {{ app_port }}"
Module Parameters
| Parameter | Required | Default | Description |
|-----------|----------|---------|-------------|
| key=value | Yes | — | The fact name and its value |
| cacheable | No | false | Store fact in the cache plugin |
See also: Ansible Performance Optimization: Speed Up Playbooks for Large-Scale Environments
Practical Examples
Example 1: Compute Facts from Gathered Data
---
- name: Dynamic facts from system info
hosts: all
tasks:
- name: Calculate memory threshold
ansible.builtin.set_fact:
memory_total_gb: "{{ ansible_memtotal_mb / 1024 | round(1) }}"
memory_threshold_mb: "{{ (ansible_memtotal_mb * 0.8) | int }}"
is_low_memory: "{{ ansible_memtotal_mb < 2048 }}"
- name: Set JVM heap based on available memory
ansible.builtin.set_fact:
jvm_heap_size: >-
{{ (ansible_memtotal_mb * 0.5) | int
if ansible_memtotal_mb > 4096
else (ansible_memtotal_mb * 0.25) | int }}m
- name: Display computed values
ansible.builtin.debug:
msg: |
Total Memory: {{ memory_total_gb }}GB
Threshold: {{ memory_threshold_mb }}MB
Low Memory: {{ is_low_memory }}
JVM Heap: {{ jvm_heap_size }}
Example 2: Facts from Registered Output
---
- name: Create facts from command output
hosts: all
tasks:
- name: Get application version
ansible.builtin.command:
cmd: /opt/app/bin/app --version
register: version_output
changed_when: false
- name: Parse version info
ansible.builtin.set_fact:
app_version: "{{ version_output.stdout | trim }}"
app_major_version: "{{ version_output.stdout.split('.')[0] }}"
needs_upgrade: "{{ version_output.stdout is version('2.0', '<') }}"
- name: Get disk usage percentage
ansible.builtin.command:
cmd: df --output=pcent / | tail -1
register: disk_output
changed_when: false
- name: Parse disk usage
ansible.builtin.set_fact:
disk_usage_pct: "{{ disk_output.stdout | trim | replace('%', '') | int }}"
disk_critical: "{{ (disk_output.stdout | trim | replace('%', '') | int) > 90 }}"
Example 3: Conditional Fact Setting
---
- name: Set facts based on conditions
hosts: all
tasks:
- name: Set package manager fact
ansible.builtin.set_fact:
pkg_manager: >-
{% if ansible_os_family == 'RedHat' %}dnf
{% elif ansible_os_family == 'Debian' %}apt
{% elif ansible_os_family == 'Suse' %}zypper
{% else %}unknown{% endif %}
- name: Set environment-specific config
ansible.builtin.set_fact:
db_host: "{{ 'db-prod.internal' if app_env == 'production' else 'db-dev.internal' }}"
log_level: "{{ 'warn' if app_env == 'production' else 'debug' }}"
replicas: "{{ 3 if app_env == 'production' else 1 }}"
- name: Set OS-specific paths
ansible.builtin.set_fact:
config_dir: "/etc/myapp"
log_dir: "/var/log/myapp"
service_name: "myapp"
when: ansible_os_family in ['RedHat', 'Debian']
- name: Override for containers
ansible.builtin.set_fact:
config_dir: "/app/config"
log_dir: "/app/logs"
when: ansible_virtualization_type == 'docker'
Example 4: Building Complex Data Structures
---
- name: Build complex facts
hosts: all
tasks:
- name: Create a server profile dictionary
ansible.builtin.set_fact:
server_profile:
hostname: "{{ ansible_fqdn }}"
ip: "{{ ansible_default_ipv4.address }}"
os: "{{ ansible_distribution }} {{ ansible_distribution_version }}"
cpus: "{{ ansible_processor_vcpus }}"
memory_gb: "{{ (ansible_memtotal_mb / 1024) | round(1) }}"
role: "{{ group_names | join(', ') }}"
- name: Build list of active network interfaces
ansible.builtin.set_fact:
active_interfaces: >-
{{ ansible_interfaces
| reject('equalto', 'lo')
| select('match', '^(eth|ens|enp)')
| list }}
- name: Create a monitoring endpoints list
ansible.builtin.set_fact:
monitoring_endpoints:
- name: health
url: "http://{{ ansible_default_ipv4.address }}:{{ app_port }}/health"
interval: 30
- name: metrics
url: "http://{{ ansible_default_ipv4.address }}:{{ app_port }}/metrics"
interval: 60
Example 5: Fact Caching Across Runs
---
- name: Cacheable facts for persistent data
hosts: all
tasks:
- name: Record deployment timestamp (persists across runs)
ansible.builtin.set_fact:
last_deployment: "{{ ansible_date_time.iso8601 }}"
deployed_version: "2.5.0"
deployed_by: "{{ lookup('env', 'USER') }}"
cacheable: true
- name: Check previous deployment (from cache)
ansible.builtin.debug:
msg: "Last deployed: {{ last_deployment | default('never') }}"
Enable fact caching in ansible.cfg:
[defaults]
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_fact_cache
fact_caching_timeout = 86400
Example 6: Merging and Transforming Data
---
- name: Data transformation with set_fact
hosts: all
vars:
default_config:
port: 8080
workers: 4
log_level: info
override_config:
workers: 8
log_level: debug
tasks:
- name: Merge configuration dictionaries
ansible.builtin.set_fact:
final_config: "{{ default_config | combine(override_config) }}"
- name: Filter and transform a list
ansible.builtin.set_fact:
critical_services: >-
{{ services
| selectattr('priority', 'equalto', 'critical')
| map(attribute='name')
| list }}
- name: Flatten nested structure
ansible.builtin.set_fact:
all_users: >-
{{ teams | map(attribute='members') | flatten | unique | sort }}
Variable Precedence
Understanding where set_fact sits in Ansible's variable precedence is critical:
1. Extra vars (--extra-vars) ← HIGHEST
2. Task vars (set in task)
3. Block vars
4. Role and include vars
5. set_fact / registered vars ← HERE
6. Play vars_files
7. Play vars
8. Host facts / cached set_facts
9. Playbook host_vars
10. Inventory host_vars
...
22. Command line values ← LOWEST
Important: set_fact overrides host_vars, group_vars, and vars files, but is overridden by --extra-vars.
See also: Ansible regex_search Filter: Search Strings with Regex Patterns (Guide)
set_fact vs vars vs register
| Feature | set_fact | vars/defaults | register | |---------|----------|---------------|----------| | When evaluated | Runtime | Load time | After task | | Scope | Host, entire play | Play/role | Host, entire play | | Dynamic | Yes | Limited (Jinja2) | Yes (output only) | | Cacheable | Yes | No | No | | Can reference other runtime data | Yes | Limited | N/A | | Use when | Need computed values | Static config | Capturing task output |
Best Practices
1. Name Your Facts Clearly
# BAD: ambiguous names
- ansible.builtin.set_fact:
ver: "2.0"
dir: "/opt"
# GOOD: descriptive, namespaced
- ansible.builtin.set_fact:
myapp_version: "2.0"
myapp_install_dir: "/opt/myapp"
2. Use Default Values
- ansible.builtin.set_fact:
api_timeout: "{{ custom_timeout | default(30) }}"
api_retries: "{{ custom_retries | default(3) }}"
3. Type Casting
- ansible.builtin.set_fact:
max_connections: "{{ raw_value | int }}"
enable_ssl: "{{ ssl_flag | bool }}"
server_list: "{{ servers_csv.split(',') | map('trim') | list }}"
Troubleshooting
Fact Not Available in Later Plays
Facts set with set_fact are only available in the current play. Use cacheable: true or pass facts via add_host:
- name: Share fact across plays
ansible.builtin.add_host:
name: fact_holder
shared_var: "{{ my_computed_value }}"
Boolean String vs Actual Boolean
# This creates a STRING "true", not boolean
- ansible.builtin.set_fact:
my_flag: "true"
# This creates an actual boolean
- ansible.builtin.set_fact:
my_flag: true
# Force boolean conversion
- ansible.builtin.set_fact:
my_flag: "{{ some_value | bool }}"
Frequently Asked Questions
What is the difference between set_fact and register in Ansible?
register captures the entire output of a task (stdout, stderr, rc, changed status) into a complex variable. set_fact creates custom variables with specific values you define, often derived from registered outputs or other data. Use register to capture task output, then set_fact to extract and transform the specific data you need.
How do I persist set_fact values across playbook runs?
Add cacheable: true to your set_fact task and configure a fact caching plugin in ansible.cfg (jsonfile, redis, or memcached). Cached facts persist between runs up to the configured timeout.
Can set_fact override variables from inventory or group_vars?
Yes. set_fact has higher precedence than inventory variables, group_vars, and host_vars. Only --extra-vars and task-level vars can override facts set with set_fact.
How do I use set_fact in a loop?
Each iteration overwrites the previous value. To build a list, use set_fact with the existing list plus the new item: my_list: "{{ my_list | default([]) + [item] }}".
Is set_fact evaluated on the controller or the target host?
set_fact is evaluated on the Ansible controller, not the remote host. It uses Jinja2 templating on the controller side to compute values, which are then stored as host-specific facts.
Related Articles
• Ansible builtin command Module Complete Guide • Ansible builtin file Module Complete Guide • Ansible Variables and Facts • Ansible Jinja2 FiltersConclusion
The ansible.builtin.set_fact module is essential for building dynamic, responsive playbooks. By computing variables at runtime based on actual system state, you can create automation that adapts to different environments, hardware configurations, and application versions without maintaining separate variable files for every scenario. Master set_fact with proper scoping, caching, and type handling to write production-grade Ansible automation.
Category: installation