AnsibleXIII · Variables and PrecedenceDesign
Names that do not collide
What you'll learn
- Explain why every variable on a host shares one flat namespace
- Apply role-prefixed naming to make collisions structurally impossible
- Identify the reserved ansible_ space and the magic variables
- Recognise that a collision produces a wrong value, not an error
Prerequisites
Verified against ansible-core 2.21.x · ansible (community package) 14.x · Python (controller) 3.12+ · ansible-lint 26.x · Molecule 26.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-11
There is one namespace per host. Not one per role, not one per play — one flat dictionary of names, shared by every role, every task and every template that runs against that host.
A role that sets port has set port for everything. A second role
that reads port gets whatever the first one left there. Nothing warns
you, because from Ansible’s perspective nothing unusual happened: a
variable had a value, and a template used it.
This is why role-prefixed naming is not a style preference. It is the only mechanism available for keeping two roles from interfering with each other.
The collision, demonstrated
Two roles, alpha and zulu, each with a service_name in its own
vars/main.yml, run in that order in one play:
$ ansible-playbook p.ymlok: [web-01.example.com] => {
"msg": "ALPHA sees alpha-svc"
}
ok: [web-01.example.com] => {
"msg": "ZULU sees zulu-svc"
}
ok: [web-01.example.com] => {
"msg": "AFTER sees zulu-svc"
}That looks safe — each role saw its own value. Now delete zulu’s
definition, so zulu reads a name it never declared:
$ ansible-playbook p.ymlok: [web-01.example.com] => {
"msg": "ALPHA sees alpha-svc"
}
ok: [web-01.example.com] => {
"msg": "ZULU sees alpha-svc"
}
ok: [web-01.example.com] => {
"msg": "AFTER sees alpha-svc"
}zulu is now configured by alpha. There is no error, no warning, and
no indication in the output that anything is unusual — zulu reports a
service name and proceeds to act on it.
Worse, the failure is order-dependent and conditional. If zulu runs
alone, service_name is undefined and the role fails loudly at
templating time, which is the good outcome. It only misbehaves when
alpha happens to run first in the same play. So the role passes its
own tests, passes in staging where it runs in isolation, and produces a
wrong value in production where the site playbook runs both.
The rule: prefix everything a role owns
Every variable a role defines or reads is prefixed with the role’s name:
# roles/webapp/defaults/main.yml
webapp_listen_port: 80
webapp_worker_count: 1
webapp_log_level: info
webapp_config_dir: /etc/webapp
Not port, workers, log_level, config_dir. The prefix makes the
collision structurally impossible: two roles cannot fight over
webapp_listen_port unless one of them is deliberately reaching into
the other’s namespace, which is visible in review.
It has a second benefit that matters more day to day. When you find
webapp_worker_count in a group_vars file, you know immediately which
role consumes it. Without the prefix, worker_count in
group_vars/prod.yml could belong to any of a dozen roles, and finding
out means reading all of them.
Three practical points:
Prefix with the role name, not an abbreviation. webapp_ for a role
called webapp. Consistency is what makes the convention searchable;
wa_ saves five characters and costs you grep.
Prefix variables the role reads, too, not just ones it defines. A
role that reads a bare environment is asking to be handed something
unrelated — and environment is a particularly bad choice, because it
is also an Ansible keyword for setting environment variables on a task.
Estate-wide values are the deliberate exception. A genuine
cross-cutting value — datacentre, environment_name, ntp_servers —
belongs in group_vars under a name that is intentionally shared, and
is consumed by several roles on purpose. Give those a consistent
estate-level prefix of their own, so the sharing is visible: corp_,
estate_, your organisation’s short name. What you want to avoid is not
sharing; it is accidental sharing.
The reserved space
Some names are not yours.
ansible_* is reserved. Facts are injected into this space, as are
connection settings such as ansible_host, ansible_user,
ansible_port, ansible_connection and ansible_python_interpreter.
Never invent a new ansible_-prefixed name; you are writing into a
namespace whose future contents are decided upstream, and a future
release adding your name is a collision you cannot fix from your side.
The connection variables in particular are load-bearing. ansible_host
sets the address Ansible connects to, and a role that sets it — perhaps
meaning “the host this application talks to” — redirects the connection
itself.
Magic variables describe the run. Upstream’s list includes
hostvars, groups, group_names, inventory_hostname,
inventory_hostname_short, ansible_play_hosts, ansible_play_batch,
ansible_playbook_python, playbook_dir, role_name, role_path,
ansible_check_mode, ansible_run_tags and omit.
The documentation says of these: “These variables cannot be set directly by the user; Ansible will always override them to reflect internal state.”
Valid names
Ansible variable names may contain letters, digits and underscores, and must begin with a letter or an underscore. Nothing else is a name.
The enforcement is not uniform, which is worth knowing. set_fact
rejects an invalid name outright:
$ ansible-playbook p3.yml[ERROR]: Task failed: Invalid variable name 'my-app-port'.
fatal: [web-01.example.com]: FAILED! => {"changed": false, "msg": "Task failed: Invalid variable name 'my-app-port'."}That is the loud, good failure. The dangerous form is a hyphenated key
in a group_vars file, which loads without complaint and then cannot be
referenced as {{ my-app-port }} — Jinja parses that as subtraction of
three names. Use underscores everywhere and the question never arises.
Two more traps in the same family:
- Group names become directory and file names. A group called
web-serversis fine in the inventory, but Jinja cannot referencegroups.web-servers; usegroups['web-servers']. Underscores in group names avoid the inconsistency. - Python keywords and Jinja globals. Avoid
class,lambda,import,range,dict,list,namespace. Some are rejected, some shadow a Jinja global and quietly break a filter expression somewhere else in the estate.
Knowledge check
Knowledge check · 4 questions
Q1. A role passes its own tests and works in staging, but produces a wrong service name in production where it runs alongside other roles. What is the most likely cause?
Q2. Which of these are genuine reasons to prefix a role variable with the role name? Select all that apply.
Q3. Ansible reliably prevents you from overriding magic variables such as inventory_hostname, as the documentation states.
Q4. Two Galaxy roles you cannot edit both use an unprefixed service_name, and the second one inherits the wrong value. What is the appropriate fix?
Passing score: 75%. Answers are checked in this browser.