Introduction
Ansible magic variables are special built-in variables automatically set by Ansible during execution. They provide information about the current host, play, inventory, and execution environment — no need to define them yourself.
Host Variables
inventory_hostname
The name of the current host as defined in inventory (not the actual hostname):
- debug:
msg: "Running on {{ inventory_hostname }}"
# Output: "Running on web-01"
# Use in templates
- template:
src: config.j2
dest: "/etc/myapp/{{ inventory_hostname }}.conf"
inventory_hostname_short
Short version (everything before the first dot):
# If inventory_hostname = "web-01.example.com"
- debug:
msg: "{{ inventory_hostname_short }}"
# Output: "web-01"
ansible_host
The actual IP or hostname to connect to:
- debug:
msg: "Connecting to {{ ansible_host }}"
# Output: "Connecting to 192.168.1.10"
hostvars
Access variables from ANY host in the inventory:
# Get DB server's IP
- debug:
msg: "DB IP: {{ hostvars['db-01']['ansible_host'] }}"
# Access facts from another host
- debug:
msg: "DB memory: {{ hostvars[groups['dbservers'][0]]['ansible_memtotal_mb'] }}"
# Use in templates (common pattern)
# templates/haproxy.cfg.j2
{% for host in groups['webservers'] %}
server {{ host }} {{ hostvars[host]['ansible_host'] }}:8080 check
{% endfor %}
Group Variables
groups
Dictionary of all groups and their hosts:
- debug:
msg: "{{ groups['webservers'] }}"
# Output: ["web-01", "web-02", "web-03"]
- debug:
msg: "All hosts: {{ groups['all'] }}"
# Loop over group members
- name: Configure upstream
template:
src: upstream.conf.j2
dest: /etc/nginx/upstream.conf
# Template uses: {% for host in groups['webservers'] %}
group_names
List of groups the current host belongs to:
- debug:
msg: "{{ inventory_hostname }} is in groups: {{ group_names }}"
# Output: "web-01 is in groups: ['webservers', 'production']"
# Conditional based on group membership
- name: Install monitoring agent
apt:
name: datadog-agent
when: "'production' in group_names"
- name: Enable debug logging
template:
src: debug-logging.conf.j2
dest: /etc/myapp/logging.conf
when: "'staging' in group_names"
Play Variables
ansible_play_hosts
All hosts in the current play (after limits/filters):
- debug:
msg: "Play targets {{ ansible_play_hosts | length }} hosts"
# Check if we're the last host
- name: Final aggregation
include_tasks: aggregate.yml
when: inventory_hostname == ansible_play_hosts[-1]
ansible_play_hosts_all
All hosts targeted by the play (before any failures):
- debug:
msg: "Originally targeted {{ ansible_play_hosts_all | length }} hosts"
play_hosts (deprecated)
Use ansible_play_hosts instead.
ansible_play_batch
Current batch of hosts in serial execution:
# With serial: 3, shows current batch of 3 hosts
- debug:
msg: "Current batch: {{ ansible_play_batch }}"
Path Variables
playbook_dir
Directory of the playbook being executed:
- debug:
msg: "Playbook is at: {{ playbook_dir }}"
# Output: "/home/user/ansible/playbooks"
# Read file relative to playbook
- copy:
src: "{{ playbook_dir }}/files/config.yml"
dest: /etc/myapp/config.yml
inventory_dir / inventory_file
Path to the inventory:
- debug:
msg: "Inventory: {{ inventory_dir }}/{{ inventory_file }}"
role_path
Current role's directory (only available inside a role):
# Inside a role
- copy:
src: "{{ role_path }}/files/custom.conf"
dest: /etc/myapp/custom.conf
ansible_search_path
List of paths Ansible searches for files/templates:
- debug: var=ansible_search_path
Execution Variables
ansible_check_mode
Whether running in check mode (--check):
- name: Run only in check mode
debug:
msg: "Dry run — no changes will be made"
when: ansible_check_mode
- name: Skip in check mode
shell: dangerous_command.sh
when: not ansible_check_mode
ansible_diff_mode
Whether running with --diff:
- debug:
msg: "Diff mode enabled"
when: ansible_diff_mode
ansible_version
Ansible version info:
- debug:
msg: "Ansible {{ ansible_version.full }}"
# Output: "Ansible 2.17.0"
- assert:
that: ansible_version.full is version('2.14', '>=')
msg: "Requires Ansible 2.14+"
ansible_verbosity
Current verbosity level (0-4):
- debug:
msg: "Extra debugging info..."
when: ansible_verbosity >= 2 # Only shows with -vv or higher
Task Variables
ansible_loop
Loop metadata (when using loop):
- name: Show loop info
debug:
msg: "Item {{ ansible_loop.index }} of {{ ansible_loop.length }}: {{ item }}"
loop: [a, b, c]
loop_control:
extended: true
| Variable | Description |
|---|---|
ansible_loop.index | Current iteration (1-based) |
ansible_loop.index0 | Current iteration (0-based) |
ansible_loop.first | True on first iteration |
ansible_loop.last | True on last iteration |
ansible_loop.length | Total items |
ansible_loop.revindex | Iterations remaining (1-based) |
ansible_failed_result
The result of the failed task (in rescue blocks):
- block:
- command: /bin/false
rescue:
- debug:
msg: "Failed: {{ ansible_failed_result.msg }}"
Connection Variables
ansible_connection
Connection type: ssh, local, winrm, docker, etc.
ansible_user
SSH user for the current host.
ansible_become
Whether privilege escalation is active.
Quick Reference Table
| Variable | Description |
|---|---|
inventory_hostname | Host name from inventory |
inventory_hostname_short | Short hostname |
ansible_host | Actual connection address |
hostvars | All host variables (dict) |
groups | All groups and hosts (dict) |
group_names | Groups current host belongs to |
ansible_play_hosts | Active hosts in current play |
playbook_dir | Playbook directory path |
role_path | Current role directory |
ansible_check_mode | True if --check |
ansible_diff_mode | True if --diff |
ansible_version | Ansible version info |
ansible_verbosity | Verbosity level (0-4) |
ansible_loop | Loop metadata |
ansible_failed_result | Failed task result (rescue) |
inventory_dir | Inventory directory |
ansible_config_file | Config file path |
Practical Examples
Generate Cluster Config
# templates/cluster.conf.j2
[cluster]
name = {{ group_names[0] }}
node_count = {{ groups[group_names[0]] | length }}
{% for host in groups[group_names[0]] %}
[node.{{ host }}]
address = {{ hostvars[host]['ansible_host'] }}
{% endfor %}
Rolling Deploy with Position Awareness
- name: Deploy with awareness
debug:
msg: >
Host {{ ansible_loop.index }}/{{ ansible_play_hosts | length }}
({{ inventory_hostname }})
{% if inventory_hostname == ansible_play_hosts[0] %}— FIRST{% endif %}
{% if inventory_hostname == ansible_play_hosts[-1] %}— LAST{% endif %}
Related Articles
Conclusion
Magic variables give you runtime context without any configuration. The most important ones: inventory_hostname (who am I), hostvars (access any host's data), groups/group_names (group membership), playbook_dir (file paths), and ansible_check_mode (dry run detection). Master these and you can write playbooks that are aware of their execution environment and adapt dynamically.