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 vs vars vs extra_vars: Variable Types Compared

By Luca Berton · Published 2024-01-01 · Category: web-servers

Complete comparison of Ansible set_fact, vars, extra_vars, group_vars, host_vars, role defaults, and registered variables.

Ansible has at least 22 places to define variables. Understanding which type to use where prevents scope bugs, precedence conflicts, and maintainability nightmares. This guide compares the most common types with clear rules.

Variable Types at a Glance

| Type | Where Defined | Scope | Precedence | Persists Across Plays | |------|-------------|-------|------------|----------------------| | extra_vars (-e) | Command line | Global | Highest (22) | Yes | | set_fact | Task | Per-host | 19 | Yes (via hostvars) | | registered | Task (register:) | Per-host | 19 | Yes (via hostvars) | | task vars | Task vars: | Single task | 18 | No | | block vars | Block vars: | Block scope | 18 | No | | play vars | Play vars: | Play scope | 14 | No | | play vars_files | Play vars_files: | Play scope | 14 | No | | host_vars | host_vars/hostname/ | Per-host | 10 | Yes | | group_vars | group_vars/groupname/ | Per-group | 7-8 | Yes | | role defaults | roles/x/defaults/ | Role scope | Lowest (1) | No |

See also: Ansible set_fact: Create & Set Runtime Variables (Complete Guide)

extra_vars: The Override

-e / --extra-vars has the highest precedence. Nothing overrides it.

# From command line
ansible-playbook site.yml -e "version=2.5.0 env=production"

# From JSON file ansible-playbook site.yml -e @deploy-vars.json

# From YAML file ansible-playbook site.yml -e @secrets.yml

# In playbook — extra_vars ALWAYS wins
- hosts: all
  vars:
    version: "1.0.0"    # This is ignored if -e version=2.5.0 is passed
  tasks:
    - ansible.builtin.debug:
        msg: "Deploying {{ version }}"
      # Output: "Deploying 2.5.0" (extra_vars wins)

When to Use extra_vars

• Runtime parameters that change per execution (version, environment, ticket ID) • CI/CD pipeline variables • Emergency overrides • One-time operations

When NOT to Use

• Defaults (use role defaults instead) • Permanent config (use group_vars/host_vars) • Anything you'd forget to pass next time

set_fact: Runtime Variables

set_fact creates host-scoped variables during playbook execution.

- name: Calculate deployment path
  ansible.builtin.set_fact:
    deploy_path: "/opt/{{ app_name }}/releases/{{ version }}"
    deploy_timestamp: "{{ ansible_date_time.iso8601 }}"

- name: Use calculated value ansible.builtin.debug: msg: "Deploying to {{ deploy_path }}"

set_fact Scope

# set_fact is per-host and persists across plays
- name: Play 1
  hosts: webservers
  tasks:
    - ansible.builtin.set_fact:
        my_fact: "from play 1"

- name: Play 2 hosts: webservers tasks: - ansible.builtin.debug: msg: "{{ my_fact }}" # ✅ Works — "from play 1"

- name: Play 3 hosts: dbservers tasks: - ansible.builtin.debug: msg: "{{ my_fact }}" # ❌ UNDEFINED — different host group # To access: hostvars[groups['webservers'][0]].my_fact

When to Use set_fact

• Computed values from task output • Transforming registered variable data • Values that depend on runtime conditions • Cross-play variable sharing (via hostvars)

set_fact vs register

# register — captures task output
- ansible.builtin.command: hostname -f
  register: hostname_result
# hostname_result.stdout, .rc, .stderr, etc.

# set_fact — creates a clean variable - ansible.builtin.set_fact: fqdn: "{{ hostname_result.stdout }}" # fqdn is just a string — cleaner to use downstream

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

vars: Play/Task-Level Variables

# Play-level vars — available to all tasks in this play
- hosts: webservers
  vars:
    http_port: 80
    max_connections: 1000
  tasks:
    - ansible.builtin.debug:
        msg: "Port: {{ http_port }}"

# Task-level vars — only for this task - name: Deploy with specific settings ansible.builtin.template: src: config.j2 dest: /etc/app.conf vars: log_level: debug # Only available in this task

When to Use Play vars

• Values that apply to a specific play • Overriding role defaults for a specific playbook • Grouping related configuration

group_vars and host_vars: Inventory Variables

inventory/
├── group_vars/
│   ├── all.yml           # All hosts
│   ├── webservers.yml    # Web server group
│   └── production.yml    # Production environment
├── host_vars/
│   ├── web01.yml         # Specific host
│   └── db01.yml
└── hosts.yml
# group_vars/webservers.yml
http_port: 80
nginx_worker_connections: 1024
ssl_enabled: true

# group_vars/production.yml log_level: warn backup_enabled: true

# host_vars/web01.yml nginx_worker_connections: 2048 # Override for this specific host

When to Use group_vars / host_vars

• Infrastructure-specific configuration • Environment differences (dev/staging/production) • Host-specific overrides • Anything that should be version-controlled with inventory

See also: ansible_date_time: Access Date, Time & Timestamp Facts in Ansible

Role Defaults: The Base Layer

# roles/nginx/defaults/main.yml
nginx_port: 80
nginx_worker_connections: 1024
nginx_log_level: info
nginx_ssl_enabled: false

Role defaults have the lowest precedence — they're overridable by everything else.

When to Use Role Defaults

• Sensible defaults that work out of the box • Values users commonly customize • Documentation of all available role parameters

Practical Decision Guide

Where should I put this variable?

Is it a runtime parameter (version, ticket ID)? └── YES → extra_vars (-e)

Is it computed from task output? └── YES → set_fact (or register + set_fact)

Does it differ between environments? └── YES → group_vars (group_vars/production.yml vs group_vars/staging.yml)

Does it differ between individual hosts? └── YES → host_vars (host_vars/web01.yml)

Is it a role configuration option? └── YES → role defaults (roles/x/defaults/main.yml)

Is it only needed in one playbook? └── YES → play vars

Is it only needed in one task? └── YES → task vars

Common Mistakes

Mistake 1: Using set_fact for static values

# ❌ BAD — set_fact for a constant
- ansible.builtin.set_fact:
    app_name: "myapp"
    app_port: 8080

# ✅ GOOD — use vars or group_vars - hosts: all vars: app_name: "myapp" app_port: 8080

Mistake 2: Expecting extra_vars to be optional

# ❌ DANGEROUS — extra_vars override EVERYTHING
# If someone passes -e http_port=443, your playbook changes behavior
- hosts: webservers
  vars:
    http_port: 80

Mistake 3: Variable precedence confusion

# What value does http_port have?
# roles/nginx/defaults/main.yml → http_port: 80
# group_vars/webservers.yml → http_port: 8080
# play vars → http_port: 443

# Answer: 443 (play vars > group_vars > role defaults) # With -e http_port=9090: 9090 (extra_vars > everything)

FAQ

What has the highest variable precedence in Ansible?

extra_vars (-e on the command line) have the absolute highest precedence (level 22). They override everything else, including task vars, set_fact, and host_vars. This is by design — they're the emergency override.

When should I use set_fact vs register?

Use register to capture the raw output of a task (stdout, stderr, rc, etc.). Use set_fact to create clean, named variables — often derived from registered output. Think of register as raw data capture and set_fact as data transformation.

Do play vars carry over to the next play?

No. Play-level vars: are scoped to that play only. To share variables across plays, use set_fact (persists in hostvars), group_vars/host_vars (inventory-level), or extra_vars (global).

How do I see all variables for a host?

Run ansible hostname -m debug -a "var=hostvars[inventory_hostname]" to dump all variables. In a playbook, use - debug: var=vars to see variables available to the current task.

Conclusion

extra_vars for runtime parameters. set_fact for computed values. group_vars/host_vars for infrastructure config. Role defaults for customizable base values. Play/task vars for playbook-specific settings. When in doubt, check the 22-level precedence order — the right choice prevents subtle bugs.

Related Articles

Ansible Variable Precedence GuideAnsible register: Save Task OutputAnsible vars_files GuideAnsible default() Filter Guide

Category: web-servers

Browse all Ansible tutorials · AnsiblePilot Home