Skip to content

Expert · Ansible in depth · topic 49 of 57 · 35 min

Variables, facts and registered results

Explanation

Variables let one playbook work for many hosts and environments. Define them in the play (vars), in files (vars_files), per group or host (group_vars/ and host_vars/), or on the command line with -e, which always wins. Facts are variables Ansible gathers about each host (OS, IP, memory). register saves a task's result for later tasks.

Why variables?

Hard-coding "httpd" or "10.0.5.20" in tasks works for one case. Variables let the same playbook install nginx in one environment and httpd in another, or point test servers at the test database. Variables are referenced with Jinja2 syntax: {{ variable_name }}. Names use letters, numbers and underscores, and start with a letter.

Where to define them

yaml
- name: Web servers
  hosts: webservers
  vars:
    web_package: httpd
    web_port: 8080
  vars_files:
    - vars/common.yml
  tasks:
    - name: Install the web server
      ansible.builtin.dnf:
        name: "{{ web_package }}"
        state: present
output
project/
├── ansible.cfg
├── inventory
├── site.yml
├── group_vars/
│   ├── all.yml           # every host
│   └── webservers.yml    # hosts in the webservers group
└── host_vars/
    └── web01.example.com.yml   # one host
Ansible loads group_vars and host_vars automatically when they sit next to the inventory or the playbook.

Who wins? Precedence, simplified

If the same variable is set in several places, Ansible uses the one with the highest precedence. The full list has over twenty levels; this simplified order (lowest to highest) covers daily work:

  1. Role defaults (roles/x/defaults/main.yml): meant to be overridden.
  2. group_vars/all
  3. group_vars/<group>
  4. host_vars/<host>
  5. Facts gathered from the host
  6. Play vars, vars_files
  7. Role vars (roles/x/vars/main.yml), task vars
  8. Extra vars on the command line (-e "web_port=9090"): ALWAYS win.

Rule of thumb: put sensible defaults low (role defaults, group_vars/all), put differences per group or host, and use -e only for one-off overrides.

Facts: what Ansible knows about each host

bash
$ ansible web01.example.com -m ansible.builtin.setup -a "filter=ansible_distribution*"
web01.example.com | SUCCESS => {
    "ansible_facts": {
        "ansible_distribution": "RedHat",
        "ansible_distribution_file_variety": "RedHat",
        "ansible_distribution_major_version": "9",
        "ansible_distribution_version": "9.4"
    },
    "changed": false
}
Facts are gathered by the "Gathering Facts" step at the start of each play (gather_facts: true by default).
yaml
- name: Show some facts
  ansible.builtin.debug:
    msg: >-
      {{ ansible_facts['hostname'] }} runs {{ ansible_facts['distribution'] }}
      {{ ansible_facts['distribution_version'] }} with
      {{ ansible_facts['memtotal_mb'] }} MB RAM at
      {{ ansible_facts['default_ipv4']['address'] }}
Use the ansible_facts['...'] form. Note that version numbers in facts are strings.

Magic variables are always available: inventory_hostname (the host's name in the inventory), groups (all groups and their hosts), hostvars (every host's variables and facts, so one host can read another's IP), group_names (the groups this host is in).

register: keep a task's result

yaml
- name: Check if the app config exists
  ansible.builtin.stat:
    path: /etc/notes/app.conf
  register: conf

- name: Report
  ansible.builtin.debug:
    msg: "Config present: {{ conf.stat.exists }}"

- name: Get the kernel version
  ansible.builtin.command: uname -r
  register: kernel
  changed_when: false        # reading is not a change

- name: Print it
  ansible.builtin.debug:
    var: kernel.stdout

Common mistakes

  • Variable names with dashes (web-port): not allowed. Use underscores.
  • Expecting a group_vars file to load when it is in the wrong folder or named differently from the group.
  • Comparing a version fact to a number: ansible_facts['distribution_major_version'] == 9 is false, because the fact is the string "9". Compare with "9", or use | int.
  • Turning off gather_facts and then using facts in the play.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Variables, facts and registered results · 1/6

{{ web_package }}

Variables let one playbook fit many hosts. Reference them with double curly braces.

Prefer a video by a teacher? Find free videos on “ansible variables facts precedence group_vars host_vars register” on YouTube ↗

In real life: One playbook for test and production at a fintech

A Mumbai fintech keeps one deploy playbook for both its UAT and production servers. group_vars/uat.yml points the app at the UAT database and sets debug logging; group_vars/prod.yml points at production and sets warning-level logs. The tasks never change; only the inventory group decides which values apply. In an emergency, -e app_maintenance=true puts the site in maintenance mode on one run.

yaml
# group_vars/prod.yml
db_host: pgsql-prod.internal.example.com
log_level: warning
app_replicas: 4

# group_vars/uat.yml
db_host: pgsql-uat.internal.example.com
log_level: debug
app_replicas: 1

Practice (0/3 done)

Task 1 · Question

web_port is 80 in group_vars/all.yml, 8080 in host_vars/web01.yml, and you run with -e web_port=9090. What does web01 use?

Task 2 · Code · Python

Model simplified variable precedence. Write effective_vars(layers) where layers is a list of dicts from LOWEST to HIGHEST precedence (for example [role_defaults, group_all, group, host, play_vars, extra_vars]). Return one dict where a key from a higher layer replaces the same key from a lower one. Do not modify the input dicts.

Task 3 · Practice

In your Ansible project, create group_vars/all.yml (ntp_server, admin_email), group_vars/webservers.yml (web_package: httpd) and host_vars for one host overriding admin_email. Write facts.yml that prints, for each host, its inventory name, distribution and version, IP address, and the effective admin_email.

Quick quiz

Question 1

1.Which has the LOWEST precedence?

Question 2

2.Which magic variable holds the host's name as written in the inventory?

Question 3

3.What does register do?

Question 4

4.Why add changed_when: false to a command that only reads data?

Question 5

5.Which module gathers facts manually?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

Which has the LOWEST precedence?

Answer: Role defaults

Role defaults are designed to be overridden.

Which magic variable holds the host's name as written in the inventory?

Answer: inventory_hostname

inventory_hostname; ansible_facts['hostname'] is what the host reports itself.

What does register do?

Answer: Saves the result of a task in a variable

Use the variable in later tasks.

Why add changed_when: false to a command that only reads data?

Answer: So it does not report "changed" every run

Reading is not a change; this keeps the recap honest.

Which module gathers facts manually?

Answer: ansible.builtin.setup

setup is what Gathering Facts runs.

Where do you put variables that apply to every host in the group "web"?Related

Answer: group_vars/web.yml

group_vars/GROUPNAME.yml applies to all hosts in that group.

From Ansible playbooks

A task fails on one host out of ten. What happens by default?Related

Answer: That host stops; the others continue

Failed hosts are removed from the rest of the play.

From Writing and running playbooks

What does Ansible show for a task whose when is false?Related

Answer: skipping

It is skipped for that host.

From Loops, conditions and error handling

Which group contains every host?Related

Answer: all

all always exists.

From Inventory and ansible.cfg

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 4 more topics in “Ansible in depth” before its exam: Loops, conditions and error handling, Jinja2 templates, Roles, collections and ansible-galaxy, RHEL System Roles and Ansible Vault.
  2. Module 10 exam: Ansible in depth · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Certifications and career. 57 topics left in the course before the final exam.

← Previous: Everyday modules and handlers