Real playbooks adapt to each host; variables and facts are how they know what to do.
Variables are referenced with Jinja2: "{{ app_port }}". When a value starts with {{, quote the whole string, or YAML will misread it as a mapping.
Where variables come from (the common places): play vars, vars_files, group_vars/ and host_vars/ next to the inventory or playbook, role defaults, -e key=value on the command line (extra vars, the highest precedence), and set_fact during a run.
Facts are variables Ansible discovers about each host at the start of a play (gather_facts: true is the default): OS, IP addresses, CPU, memory, mounts. Access them through ansible_facts, like ansible_facts['distribution'] or ansible_facts['default_ipv4']['address']. The older top-level form (ansible_distribution) still works but is being phased out. Turn gathering off with gather_facts: false when you don't need it; it speeds up runs.
Special variables: inventory_hostname, group_names, groups['web'] (all hosts in a group), hostvars['db1'] (another host's variables and facts).
register saves a task's result: register: result, then use result.stdout, result.rc, result.changed. Use ansible.builtin.debug with var: or msg: to print values while developing.
Example
- name: Variables and facts demo
hosts: localhost
connection: local
vars:
app_name: shop
app_port: 8080
tasks:
- name: Show OS facts
ansible.builtin.debug:
msg: "{{ app_name }} on {{ ansible_facts['distribution'] }} {{ ansible_facts['distribution_version'] }}"
- name: Check what listens on the port
ansible.builtin.command: "ss -ltn sport = :{{ app_port }}"
register: listen
changed_when: false # read-only command
- name: Use the registered result
ansible.builtin.debug:
msg: "Port {{ app_port }} busy: {{ listen.stdout_lines | length > 1 }}"
# ansible-playbook vars.yml -e app_port=9090 # extra vars override play varsRemember
- Quote values that start with {{
- Facts live under ansible_facts; turn gathering off when unneeded
- register captures results; -e has the highest precedence
Watch out for
- Writing msg: {{ x }} unquoted, which YAML parses as a dictionary and errors.