Variables in Ansible allow playbooks to adapt dynamically across environments (dev vs. staging vs. production) without hardcoding values into task logic.
However, because variables can be defined in over 20 different places, understanding Variable Precedence and System Facts is vital for predicting variable evaluation and debugging scope conflicts.
Topic 1: System Facts (ansible_facts) & Performance Optimization
When a play begins, Ansible automatically executes an implicit initial task called Gathering Facts using the setup module. Facts are system properties discovered dynamically on managed nodes:
ansible_facts['distribution']: OS Distribution (e.g.,Ubuntu,RedHat,Debian).ansible_facts['distribution_major_version']: OS Major Version (e.g.,22,9).ansible_facts['default_ipv4']['address']: Primary IP address.ansible_facts['memtotal_mb']: Total RAM in megabytes.ansible_facts['processor_vcpus']: Total CPU core count.ansible_facts['os_family']: OS Family (Debian,RedHat,Archlinux).
Disabling Fact Gathering to Optimize Speed:
Gathering facts takes 2–5 seconds per host over SSH. If a playbook only performs simple API calls or static deployments that do not rely on system facts, disable fact gathering to drastically speed up execution:
- name: High Performance API Orchestration
hosts: all
gather_facts: false # Disables implicit setup module task
tasks:
- name: Trigger webhook endpoint
ansible.builtin.uri:
url: https://api.acme.com/trigger
method: POST
Topic 2: Custom Local Facts (/etc/ansible/facts.d/*.fact)
In addition to standard hardware/OS facts, managed nodes can define Custom Local Facts.
Any file placed in /etc/ansible/facts.d/ ending in .fact (INI or JSON format) or executable scripts returning JSON are automatically gathered by the setup module and exposed inside ansible_facts.ansible_local:
# Managed Node File: /etc/ansible/facts.d/datacenter.fact
[location]
tier = production
datacenter_code = us-east-dc1
rack_id = A-14
owner_team = checkout
# Playbook accessing Custom Local Facts
- name: Display custom datacenter location fact
ansible.builtin.debug:
msg: "Host is in Datacenter {{ ansible_facts.ansible_local.datacenter.location.datacenter_code }}, Rack {{ ansible_facts.ansible_local.datacenter.location.rack_id }}"
Topic 3: The 22-Level Variable Precedence Hierarchy
When the same variable name (e.g., http_port) is defined in multiple locations, Ansible evaluates the final value according to a strict 22-level precedence hierarchy. Higher levels override lower levels:
┌─────────────────────────────────────────────────────────────┐
│ 22 (Highest): Extra Vars (-e "var=value") │ ← HIGHEST: ALWAYS WINS!
├─────────────────────────────────────────────────────────────┤
│ 21: Task Block vars │
├─────────────────────────────────────────────────────────────┤
│ 20: Task vars (only for specific task) │
├─────────────────────────────────────────────────────────────┤
│ 19: Role vars (roles/x/vars/main.yml) │
├─────────────────────────────────────────────────────────────┤
│ 16: Play vars (vars: inside play) │
├─────────────────────────────────────────────────────────────┤
│ 13: Host Facts / ansible_facts │
├─────────────────────────────────────────────────────────────┤
│ 10: host_vars files (host_vars/web1.yml) │
├─────────────────────────────────────────────────────────────┤
│ 6: group_vars files (group_vars/webservers.yml) │
├─────────────────────────────────────────────────────────────┤
│ 3: group_vars/all.yml │
├─────────────────────────────────────────────────────────────┤
│ 1 (Lowest): Role Defaults (roles/x/defaults/main.yml) │ ← LOWEST: EASILY OVERRIDDEN
└─────────────────────────────────────────────────────────────┘
Production Golden Rules of Variable Placement:
- Use Role Defaults (
roles/x/defaults/main.yml) for default variable settings inside roles that users are EXPECTED to override ingroup_varsorhost_vars. - Use Role Vars (
roles/x/vars/main.yml) ONLY for internal role constants that users SHOULD NEVER override. - Use
group_vars/all.ymlfor organization-wide defaults (NTP servers, domain names, syslog endpoints). - Use
group_vars/<group_name>.ymlfor environment or tier defaults (webservers.yml,dbservers.yml). - Use Extra Vars (
ansible-playbook -e "var=val") ONLY for temporary CLI overrides or CI/CD build number injections.
Topic 4: Variable Naming Standards & Preventing Namespace Pollution
Avoid generic variable names like port: 80, user: admin, or version: 1.0.
If two different roles define port: 80 and port: 5432, the last evaluated role will silently overwrite the variable for the entire play!
Red Hat Standard: Prefix Role Variables: Always prefix variable names with the role name:
-
Role
nginx:nginx_http_port: 80nginx_worker_processes: 4nginx_config_path: /etc/nginx/nginx.conf -
Role
postgresql:postgresql_port: 5432postgresql_max_connections: 200postgresql_data_dir: /var/lib/postgresql/data
# Verifying variable resolution via debug module
- name: Debug resolved role variable
ansible.builtin.debug:
var: nginx_http_port
Common mistake: Leaving fact gathering on for plays that never read a fact. It adds a full round trip per host to every run — and the opposite mistake is worse: setting gather_facts: false on a play whose template quietly uses ansible_default_ipv4, which then renders empty rather than failing.