AnsibleXXII · Roles and ReuseRoles and reuse
Role anatomy and where roles are found
What you'll learn
- Name every directory in a role and state what Ansible loads from it without being asked
- Predict which file a role reads for a given kind of content
- State the role search path in order and read it off a failure message
- Detect a role name that resolves to a different role than you intended
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
A role is a directory with a conventional layout. There is no registration
step, no manifest listing its contents, and no import statement naming its
files. You write roles: [webapp] in a play, and Ansible walks a fixed set
of subdirectory names underneath roles/webapp/, loading whatever it finds.
That is the whole mechanism, and it is worth stating plainly because the
convention does all the work. Nothing tells you that defaults/main.yml
holds variables — the path is the declaration. A file in the wrong
directory is not an error. It is silence.
The directories
ansible-galaxy init writes the canonical skeleton. This is the fastest way
to see what the convention actually is, rather than what you remember of it:
ansible-galaxy init webapp
find webapp -type f | sort$ ansible-galaxy init webapp && find webapp | sort- Role webapp was created successfully
webapp
webapp/README.md
webapp/defaults
webapp/defaults/main.yml
webapp/files
webapp/handlers
webapp/handlers/main.yml
webapp/meta
webapp/meta/main.yml
webapp/tasks
webapp/tasks/main.yml
webapp/templates
webapp/tests
webapp/tests/inventory
webapp/tests/test.yml
webapp/vars
webapp/vars/main.ymlSix directories carry a main.yml that Ansible loads by itself when the role
runs. Two do not, and the difference is the thing people get wrong:
| Directory | Auto-loaded main.yml? | What it is for |
|---|---|---|
tasks/ | Yes — the role’s entry point | The tasks the role runs |
handlers/ | Yes | Handlers, merged into the parent play’s namespace |
defaults/ | Yes | Variables at the bottom of precedence — the role’s public interface |
vars/ | Yes | Variables high in precedence — internal constants |
meta/ | Yes | Role metadata: dependencies, allow_duplicates, Galaxy info |
tests/ | No — not part of a run | A sample inventory and playbook for the role author |
files/ | No | Changes where copy, script and unarchive look for a relative source |
templates/ | No | Changes where template looks for a relative source |
files/ and templates/ do not load anything. They alter lookup: inside a
role, ansible.builtin.template: src=nginx.conf.j2 resolves against
roles/webapp/templates/ first. That is why a role’s templates need no path
prefix and why moving a task out of a role breaks its src silently.
Three more directories exist and are not in the skeleton, because most roles do not need them:
library/— modules shipped inside the role, available to it without a collectionmodule_utils/— shared Python for those moduleslookup_plugins/,filter_plugins/,action_plugins/and the rest of the plugin families — plugins scoped to the role
Where a bare role name is looked up
roles: [webapp] is a name, not a path. Ansible resolves it against a search
path, and the fastest way to see that path exactly is to ask for a role that
does not exist:
$ ansible-playbook -i inventory.ini missing.yml[ERROR]: The role 'nonexistent_role' was not found in:
/srv/automation/roles:/home/deploy/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles:/srv/automation
Origin: /srv/automation/missing.yml:3:11
1 - hosts: local
2 gather_facts: false
3 roles: [nonexistent_role]
^ column 11In order:
roles/beside the playbook —/srv/automation/roleshere. This is the project’s own roles directory and it wins.roles_path— the configured list, three entries by default.- The playbook’s own directory — a fallback that occasionally surprises people who have a stray directory named like a role.
Collections come before all of this when you use a fully qualified name.
namespace.collection.rolename bypasses the search path entirely and looks
inside the installed collection, which is the unambiguous form and the one to
prefer for anything you did not write.
ansible-config dump | grep -i ROLES_PATH$ ansible-config dump | grep -i ROLES_PATHDEFAULT_ROLES_PATH(default) = ['/home/deploy/.ansible/roles', '/usr/share/ansible/roles', '/etc/ansible/roles']The shadowing failure
Two roles named nginx — one in the repository under roles/nginx, one
installed into ~/.ansible/roles/nginx by a requirements.yml run months
ago. The repository copy wins for you. On a colleague’s controller, where the
repository copy was never added, the Galaxy copy wins. Same playbook, same
inventory, same command, two different sets of tasks against production.
Nothing warns you. The name resolved; a role ran; the play succeeded.
for d in ./roles ~/.ansible/roles /usr/share/ansible/roles /etc/ansible/roles; do
[ -d "$d" ] || continue
for r in "$d"/*/; do
[ -d "$r" ] && printf '%s\t%s\n' "$(basename "$r")" "$d"
done
done | sort | uniq -c | sort -rn | head -20The operational habit that costs nothing: run --list-tasks before a real
run and read the role prefixes. Every task inside a role is displayed as
rolename : task name, so a role that resolved somewhere unexpected shows
up as a task list you do not recognise.
$ ansible-playbook -i inventory.ini site.yml --list-tasksplaybook: site.yml
play #1 (web): configure the web tier TAGS: []
tasks:
webapp : install packages TAGS: []
webapp : write configuration TAGS: []
webapp : start and enable the service TAGS: []Knowledge check
Knowledge check · 4 questions
Q1. A role author puts a Jinja template at roles/webapp/templates/nginx.conf.j2 and a task in the role uses src: nginx.conf.j2 with no path. What makes that work?
Q2. Which role directories does Ansible load a main.yml from automatically when the role runs? Select all that apply.
Q3. A file written to roles/webapp/task/main.yml, with the directory name in the singular, produces a warning when the role runs.
Q4. You run a play on your controller and it behaves differently from the same play on a colleague controller, same repository and same inventory. Which cause fits that symptom best?
Passing score: 75%. Answers are checked in this browser.