AnsiblePilot — Master Ansible Automation

AnsiblePilot is the leading resource for learning Ansible automation, DevOps, and infrastructure as code. Browse over 1,400 tutorials covering Ansible modules, playbooks, roles, collections, and real-world examples. Whether you are a beginner or an experienced engineer, our step-by-step guides help you automate Linux, Windows, cloud, containers, and network infrastructure.

Popular Topics

About Luca Berton

Luca Berton is an Ansible automation expert, author of 8 Ansible books published by Apress and Leanpub including "Ansible for VMware by Examples" and "Ansible for Kubernetes by Example", and creator of the Ansible Pilot YouTube channel. He shares practical automation knowledge through tutorials, books, and video courses to help IT professionals and DevOps engineers master infrastructure automation.

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 with cacheable: 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 GuideAnsible builtin file Module Complete GuideAnsible Variables and FactsAnsible Jinja2 Filters

Conclusion

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

Browse all Ansible tutorials · AnsiblePilot Home