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.builtin.set_fact Module: Set Variables Dynamically (Complete Guide)

By Luca Berton · Published 2026-04-03 · Category: linux-administration

Complete guide to ansible.builtin.set_fact module. Set variables dynamically during playbook execution, cache facts across plays, use with conditionals.

The ansible.builtin.set_fact module creates or modifies variables (facts) at runtime. Unlike vars, facts set with set_fact persist across tasks and can be cached across playbook runs.

Basic Usage

- name: Set a simple variable
  ansible.builtin.set_fact:
    app_version: "2.5.1"
    deploy_path: "/opt/myapp"

- name: Use the variable
  ansible.builtin.debug:
    msg: "Deploying version {{ app_version }} to {{ deploy_path }}"

See also: Ansible Facts: Gather, Use, and Create Custom Facts (Complete Guide)

Set Facts from Registered Variables

- name: Get current user
  ansible.builtin.command: whoami
  register: whoami_result

- name: Store as fact
  ansible.builtin.set_fact:
    current_user: "{{ whoami_result.stdout }}"

Conditional set_fact

- name: Set environment-specific config
  ansible.builtin.set_fact:
    db_host: "{{ 'db-prod.example.com' if env == 'production' else 'db-dev.example.com' }}"
    db_port: "{{ 5432 if env == 'production' else 5433 }}"
- name: Set fact based on OS
  ansible.builtin.set_fact:
    package_manager: apt
  when: ansible_os_family == 'Debian'

- name: Set fact for RedHat
  ansible.builtin.set_fact:
    package_manager: dnf
  when: ansible_os_family == 'RedHat'

See also: Ansible set_fact Module: Set Variables Dynamically (Complete Guide 2026)

Complex Data Structures

Set a dictionary

- name: Set database config
  ansible.builtin.set_fact:
    db_config:
      host: db.example.com
      port: 5432
      name: myapp
      user: appuser

Set a list

- name: Build server list dynamically
  ansible.builtin.set_fact:
    server_list: "{{ server_list | default([]) + [item] }}"
  loop: "{{ groups['webservers'] }}"

Merge dictionaries

- name: Merge with existing config
  ansible.builtin.set_fact:
    app_config: "{{ app_config | default({}) | combine({'new_key': 'new_value'}) }}"

Cacheable Facts

Persist facts across playbook runs using fact caching:

- name: Set cacheable fact
  ansible.builtin.set_fact:
    last_deploy_time: "{{ ansible_date_time.iso8601 }}"
    cacheable: true

Configure fact caching in ansible.cfg:

[defaults]
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts_cache
fact_caching_timeout = 86400

See also: Ansible facts: Gather, Use, and Create Custom Facts

set_fact vs vars vs register

Featureset_factvarsregister
ScopeHost-level, persists across playsPlay/block/task levelTask-level
DynamicYes — computed at runtimeLimited with Jinja2Output only
CacheableYes (with cacheable: true)NoNo
OverridesHigher precedence than varsLower precedenceN/A

Practical Examples

Calculate derived values

- name: Get filesystem usage
  ansible.builtin.command: df -BG / --output=avail
  register: disk_result

- name: Parse available disk space
  ansible.builtin.set_fact:
    disk_available_gb: "{{ disk_result.stdout_lines[1] | regex_search('([0-9]+)') | int }}"
    disk_low: "{{ (disk_result.stdout_lines[1] | regex_search('([0-9]+)') | int) < 10 }}"

Build configuration from inventory

- name: Build upstream server list for nginx
  ansible.builtin.set_fact:
    upstream_servers: "{{ groups['appservers'] | map('extract', hostvars, 'ansible_host') | list }}"

FAQ

When should I use set_fact vs register?

Use register to capture command output. Use set_fact to create computed/derived variables or transform registered data.

Do set_fact variables survive between plays?

Yes! Facts set with set_fact persist for the host across all plays in the same playbook run. With cacheable: true, they survive across runs.

How do I unset a fact?

You can't directly unset a fact, but you can set it to omit or an empty value: my_fact: ""

Basic set_fact

- name: Set a variable
  ansible.builtin.set_fact:
    app_version: "2.5.0"
    deploy_path: "/opt/myapp-2.5.0"

- debug:
    msg: "Deploying v{{ app_version }} to {{ deploy_path }}"

Set Facts from Task Results

- name: Get hostname
  ansible.builtin.command: hostname -f
  register: hostname_result
  changed_when: false

- name: Store as fact
  ansible.builtin.set_fact:
    server_fqdn: "{{ hostname_result.stdout }}"

Conditional Facts

- name: Set environment-specific config
  ansible.builtin.set_fact:
    db_host: "{{ 'db.prod.com' if env == 'production' else 'db.dev.com' }}"
    debug_mode: "{{ env != 'production' }}"

Computed Facts

- name: Calculate values
  ansible.builtin.set_fact:
    total_memory_mb: "{{ ansible_memtotal_mb }}"
    java_heap_mb: "{{ (ansible_memtotal_mb * 0.75) | int }}"
    worker_count: "{{ ansible_processor_vcpus * 2 }}"

Build Complex Data Structures

- name: Build server config
  ansible.builtin.set_fact:
    server_config:
      hostname: "{{ inventory_hostname }}"
      ip: "{{ ansible_default_ipv4.address }}"
      os: "{{ ansible_distribution }}"
      roles: "{{ group_names }}"

- name: Build list incrementally
  ansible.builtin.set_fact:
    all_ips: "{{ all_ips | default([]) + [hostvars[item].ansible_host] }}"
  loop: "{{ groups['webservers'] }}"

Cacheable Facts (Persistent)

- name: Set persistent fact
  ansible.builtin.set_fact:
    last_deploy_time: "{{ ansible_date_time.iso8601 }}"
    cacheable: true  # Survives between plays

Requires fact_caching in ansible.cfg:

[defaults]
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts

set_fact vs vars vs register

MethodScopeWhen Set
set_factHost, rest of playRuntime (dynamic)
varsTask/play/roleBefore execution (static)
registerHost, rest of playFrom task output
group_varsGroup hostsInventory load time
include_varsHost, rest of playFile load time

Common Patterns

Default values

- set_fact:
    config: "{{ user_config | default(default_config) | combine({'timestamp': now}) }}"

Cross-host facts

- set_fact:
    primary_ip: "{{ hostvars[groups['primary'][0]].ansible_host }}"

FAQ

Does set_fact persist between playbook runs?

Only with cacheable: true and fact_caching configured. Otherwise, facts reset each run.

Can I unset a fact?

No direct way. Set it to "", null, or omit:

- set_fact:
    my_var: null

set_fact in a loop — does it accumulate?

Each iteration replaces the fact. To accumulate, build on the previous value:

- set_fact:
    items: "{{ items | default([]) + [item] }}"
  loop: [a, b, c]

Basic set_fact

- set_fact:
    app_version: "2.5.0"
    deploy_time: "{{ ansible_date_time.iso8601 }}"
    full_name: "{{ first_name }} {{ last_name }}"

From Command Output

- command: git rev-parse --short HEAD
  register: git_output
  changed_when: false

- set_fact:
    git_commit: "{{ git_output.stdout }}"
    deploy_tag: "deploy-{{ git_output.stdout }}-{{ ansible_date_time.epoch }}"

Computed Values

- set_fact:
    worker_count: "{{ ansible_processor_vcpus * 2 }}"
    memory_limit: "{{ (ansible_memtotal_mb * 0.75) | int }}m"
    is_production: "{{ env == 'production' }}"
    app_url: "{{ 'https' if enable_ssl else 'http' }}://{{ server_name }}:{{ app_port }}"

Build Lists and Dicts

# Append to list
- set_fact:
    server_list: "{{ server_list | default([]) + [inventory_hostname] }}"

# Build dict
- set_fact:
    host_info:
      hostname: "{{ inventory_hostname }}"
      ip: "{{ ansible_default_ipv4.address }}"
      os: "{{ ansible_distribution }}"
      cpu: "{{ ansible_processor_vcpus }}"

Conditional set_fact

- set_fact:
    package_manager: apt
  when: ansible_os_family == "Debian"

- set_fact:
    package_manager: dnf
  when: ansible_os_family == "RedHat"

# Ternary (one-liner)
- set_fact:
    db_host: "{{ 'db.prod.com' if env == 'production' else 'localhost' }}"

Cacheable Facts (Persist Across Plays)

- set_fact:
    discovered_version: "2.5.0"
    cacheable: true  # Survives between plays when fact_caching is enabled
# ansible.cfg (required for cacheable)
[defaults]
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible-facts
fact_caching_timeout = 86400

Transform Data

# Filter list
- set_fact:
    active_hosts: "{{ all_hosts | selectattr('status', 'eq', 'active') | list }}"

# Extract field
- set_fact:
    hostnames: "{{ servers | map(attribute='name') | list }}"

# Merge dicts
- set_fact:
    final_config: "{{ default_config | combine(env_config) | combine(host_config) }}"

Loop with set_fact

# Build a dict from loop
- set_fact:
    service_ports: "{{ service_ports | default({}) | combine({item.name: item.port}) }}"
  loop:
    - { name: web, port: 80 }
    - { name: api, port: 8080 }
    - { name: db, port: 5432 }
# Result: { web: 80, api: 8080, db: 5432 }

set_fact vs vars vs register

MethodScopeTimingUse Case
set_factHost, persists in playRuntimeDynamic values
varsTask/play/roleCompile timeStatic values
registerHost, current playAfter taskCapture output
group_varsGroup hostsLoad timeInventory config

FAQ

Does set_fact persist across plays?

By default, yes — within the same playbook run. For cross-playbook persistence, use cacheable: true with fact caching enabled.

Can I unset a fact?

No direct way. Set it to null or "", or use meta: clear_facts to clear all facts.

set_fact vs vars?

set_fact creates host-level facts at runtime. vars are evaluated at parse time. Use set_fact when the value depends on runtime data (command output, conditionals).

Basic set_fact

- ansible.builtin.set_fact:
    app_url: "https://{{ inventory_hostname }}:{{ app_port }}"
    full_name: "{{ first_name }} {{ last_name }}"

Conditional set_fact

- set_fact:
    package_manager: apt
  when: ansible_os_family == "Debian"

- set_fact:
    package_manager: yum
  when: ansible_os_family == "RedHat"

From Registered Results

- command: cat /opt/myapp/VERSION
  register: version_output
  changed_when: false

- set_fact:
    app_version: "{{ version_output.stdout | trim }}"

Complex Data Structures

- set_fact:
    server_config:
      host: "{{ ansible_default_ipv4.address }}"
      port: "{{ app_port }}"
      workers: "{{ ansible_processor_vcpus * 2 }}"
      env: "{{ 'production' if is_prod else 'staging' }}"

Cacheable Facts

# Persists across plays (with fact caching enabled)
- set_fact:
    deployment_timestamp: "{{ ansible_date_time.iso8601 }}"
    cacheable: true

Append to List

- set_fact:
    my_list: "{{ my_list | default([]) + [new_item] }}"

# Build list from loop
- set_fact:
    server_ips: "{{ server_ips | default([]) + [hostvars[item]['ansible_default_ipv4']['address']] }}"
  loop: "{{ groups['webservers'] }}"

Merge Dictionaries

- set_fact:
    config: "{{ config | default({}) | combine({'new_key': 'new_value'}) }}"

set_fact vs vars

Featureset_factvars
ScopePer-host, persists across playsPer-play or per-task
Dynamic✅ Computed at runtime❌ Static (evaluated once)
Conditional✅ Can use when❌ Always set
Cacheable✅ With cacheable: true

Cross-Host Access

# set_fact on web1
- set_fact:
    app_version: "2.5.0"
  when: inventory_hostname == "web1"

# Access from any host
- debug:
    msg: "Web1 runs version {{ hostvars['web1']['app_version'] }}"

FAQ

set_fact persists between plays?

Yes — set_fact values persist for the host across all plays in the same playbook run. With cacheable: true and fact caching, they persist across runs.

How to unset a fact?

You can't truly unset it, but you can set it to undefined: this isn't directly supported. Set it to empty or use default() pattern.

set_fact in a loop?

Each iteration overwrites the previous value. To accumulate, use the append pattern: "{{ list + [item] }}".

Category: linux-administration

Browse all Ansible tutorials · AnsiblePilot Home