Standard Ansible Role Git Repository Layout
Bottom line
The standard Ansible role repository layout is defined by a seven-directory convention (tasks, handlers, templates, files, vars, defaults, meta) auto-generated by ansible-galaxy role init, which also produces README.md and a tests/ directory. The repository root must be named after the role for Galaxy publishing. A role is a self-contained directory tree.
In production projects, the role lives inside a roles/ directory of a larger playbook project with inventory files, group_vars/, host_vars/, tier-specific playbooks, and a site.yml master playbook. The landscape is shifting: standalone role repositories are being superseded by collections, where roles live under roles/ within the collection structure alongside plugins, modules, and a galaxy.yml manifest. Confidence in these conventions is high - they come directly from official Ansible documentation and are corroborated across multiple independent technical sources.
Key findings
-
Finding:
ansible-galaxy role init <name>is the canonical way to scaffold a role. It creates 8 subdirectories and 8 files by default, though the official docs count only seven "main standard" directories (excludingtests/). Every role repository root should match the role name for Galaxy compatibility. (Source: official Ansible docs, Galaxy Developer Guide) -
Finding: The seven standard directories each have a specific, non-negotiable purpose.
tasks/for execution logic,handlers/for event-driven actions,defaults/for overridable low-precedence variables,vars/for high-precedence internal constants,templates/for Jinja2 files,files/for static assets, andmeta/for Galaxy metadata and role dependencies. A role needs only one of these to be valid. (Source: official Ansible Roles docs; Adfinis Ansible Guide) -
Finding: Role repositories are not the whole project. The full recommended project layout places roles inside a
roles/directory alongsideinventory/,group_vars/,host_vars/, per-tier playbooks (webservers.yml,dbservers.yml), and asite.ymlmaster playbook. This is documented in the official Ansible best practices and reinforced across tutorials. (Source: official Ansible Best Practices; ComputingForGeeks 2026 tutorial) -
Finding: Collections are replacing standalone role repositories as the preferred distribution format. In a collection, roles live under
roles/role_name/and follow the same directory structure, but with two key restrictions. Roles can't contain embedded plugins (they must go in the collection'splugins/directory), and role names are restricted to lowercase alphanumeric characters plus underscores. The Adfinis guide explicitly recommends collections over single-role repos. (Source: Ansible Collection Structure docs; Adfinis Ansible Guide) -
Finding: The practical pain points with the role layout include: scattered logic across many directories making it harder to trace execution flow; cross-platform boilerplate when writing OS-aware roles; and community debate about whether roles should be "large and server-role-focused" or "small and function-generic." (Source: Roelof Jan Elsinga blog; Ansible Forum discussion; Ansible GitHub issue #52759)
Background
Ansible roles were introduced as a structured alternative to monolithic playbooks. The directory convention was designed so that Ansible can auto-discover tasks, handlers, variables, templates. Files without explicit path configuration - it simply looks for main.yml files in each standard subdirectory.
The ansible-galaxy CLI tool, bundled with Ansible, provides role init to scaffold this structure. Roles are shared via Ansible Galaxy, a public registry that imposes naming and metadata conventions. Since Ansible 2.9/2.10, the collection format was introduced as a higher-level packaging mechanism that can bundle multiple roles, modules. Plugins together, and this has become the recommended distribution method.
Current state
As of 2026 (latest stable: ansible-core 2.19/Ansible 12):
- The
ansible-galaxy role initcommand still produces the same skeleton:defaults/,files/,handlers/,meta/,tasks/,templates/,tests/,vars/, plusREADME.md. - Official docs reference seven main standard directories (excluding
tests/andREADME.md), thoughtests/is universally generated and used. - Standalone roles still work and are still published to Galaxy, but collections are the forward-looking standard. The Adfinis guide notes: "We generally recommend collections over single-role repositories. While there is no official 'don't use single-role repos' announcement yet, and they still work, collections are the way forward."
- Role argument validation via
meta/argument_specs.yml(since Ansible 2.11) adds a formal interface layer. DEFAULT_PRIVATE_ROLE_VARS(since ansible-core 2.15) changed howvars:in theroles:section of playbooks scope, making role variables no longer leak into play scope by default.- Ansible-core 2.19 introduced significant templating changes that may require updating existing roles.
Technical details: directory-by-directory reference
| Directory | Required? | Purpose | Ansible behavior |
|---|---|---|---|
tasks/ |
No (but practically always) | Core execution logic. main.yml is the entry point. |
Auto-loaded at role invocation. Supports include_tasks/import_tasks for splitting. |
handlers/ |
No | Event-driven tasks triggered by notify. Run once at end of play. |
main.yml auto-loaded. Deduplicates: if 5 tasks notify the same handler, it runs once. |
defaults/ |
No | Overridable variables with lowest precedence. The role's public API. | main.yml auto-loaded. Intended for values consumers should override. |
vars/ |
No | High-precedence variables. Internal constants. | main.yml auto-loaded. Can use OS-specific files loaded via include_vars or first_found. |
templates/ |
No | Jinja2 template files (.j2 extension convention). |
template module auto-searches here. Recommended: mirror target filesystem path inside. |
files/ |
No | Static files deployed via copy/script modules. |
copy and script modules auto-search here. Recommended: mirror target filesystem path. |
meta/ |
No (recommended) | Galaxy metadata (author, license, platforms, tags) and dependencies. |
main.yml auto-loaded. Required for Galaxy publishing of standalone roles. |
tests/ |
No | Test playbook (test.yml) and inventory. |
Generated by ansible-galaxy init. Not counted among "seven main standard" directories. |
README.md |
No (recommended) | Role documentation. | Generated by ansible-galaxy init. Galaxy renders it on the role page. |
library/ |
No | Custom Ansible modules (Python). | Auto-added to module search path for this role and subsequent roles. Standalone roles only. |
module_utils/ |
No | Shared Python code for custom modules. | Importable by modules in library/. Standalone roles only. |
filter_plugins/ |
No | Custom Jinja2 filters. | Available to this role and subsequent roles. Standalone roles only. |
lookup_plugins/ |
No | Custom lookup plugins. | Available to this role and subsequent roles. Standalone roles only. |
meta/argument_specs.yml |
No | Role argument validation specification (Ansible 2.11+). | Validates role parameters before execution. Recommended over inline specs in meta/main.yml. |
The full project layout (beyond the role)
A production Ansible project wraps roles in a larger structure:
project/
├── ansible.cfg # Project-specific config (roles_path, collections_paths)
├── inventory/
│ ├── production # Production hosts inventory
│ └── staging # Staging hosts inventory
├── group_vars/
│ ├── all.yml # Variables for all hosts
│ ├── webservers.yml # Variables for webservers group
│ └── dbservers.yml # Variables for database group
├── host_vars/
│ └── hostname1.yml # Per-host variable overrides
├── library/ # Project-level custom modules (optional)
├── filter_plugins/ # Project-level custom filters (optional)
├── requirements.yml # Pinned role/collection dependencies
├── site.yml # Master playbook (includes all tier playbooks)
├── webservers.yml # Playbook targeting webservers group
├── dbservers.yml # Playbook targeting dbservers group
└── roles/
├── common/ # The "common" role (standard layout inside)
├── webtier/ # The "webtier" role
└── monitoring/ # The "monitoring" role
Key conventions from the official best practices:
site.ymlis the master playbook that includes per-tier playbooks.- Tier playbooks (
webservers.yml,dbservers.yml) map host groups to their roles. group_vars/holds variables by function (webservers, dbservers) and geography (atlanta, boston).- Tags enable selective execution:
ansible-playbook site.yml --tags ntp. requirements.ymlpins role and collection versions for reproducibility.
Jeff Geerling (author of many popular Ansible roles) recommends project-local role and collection paths (set via ansible.cfg) so each project tracks its own dependencies independently.
Collections vs standalone roles
When a role lives inside a collection, the structure changes at the collection level:
collection/
├── galaxy.yml # Collection manifest (required)
├── README.md
├── docs/ # Collection-level documentation
├── meta/
│ └── runtime.yml # Collection metadata, plugin routing, deprecations
├── plugins/ # All plugins (modules, lookup, filter, etc.)
│ ├── modules/
│ ├── lookup/
│ └── filter/
├── roles/ # Collection roles
│ ├── role1/ # Same internal layout as standalone role
│ │ ├── tasks/
│ │ ├── defaults/
│ │ ├── vars/
│ │ ├── templates/
│ │ ├── files/
│ │ ├── handlers/
│ │ └── meta/
│ └── role2/
├── playbooks/ # Collection playbooks (FQCN-referencable)
└── tests/ # ansible-test integration tests
Critical differences for collection-hosted roles:
- No embedded plugins - roles can't have
library/,filter_plugins/, etc. All plugins live in the collection'splugins/directory. - Role names restricted - lowercase alphanumeric + underscore, must start with alpha character.
role_nameinmeta/main.ymlis ignored - Galaxy uses the directory name.meta/main.ymlis optional (but recommended) for collection roles, whereas it's required for standalone roles submitted to Galaxy.
Variable precedence in roles
Understanding where to place variables is critical. Within roles, the hierarchy (lowest to highest):
defaults/main.yml- lowest, designed to be overridden- Inventory
group_vars/ - Inventory
host_vars/ - Playbook
vars:/vars_files vars/main.yml- high, for internal constants- Task
vars: - Extra vars (
-e) - highest, overrides everything
Best practice: put most values in defaults/ so consumers can customize without forking. Reserve vars/ for OS-specific paths, service names, and internal constants. Always namespace variables with the role name prefix (e.G., nginx_port, not port).
Limitations and critiques
- Scattered visibility: the directory layout means you can't read a playbook and understand what it executes; you must navigate through multiple
main.ymlfiles across subdirectories. (Source: Roelof Jan Elsinga) - Cross-platform boilerplate: writing roles that work across RHEL and Debian requires
when: ansible_os_familyconditionals and OS-specific variable files, which creates maintenance overhead. This was raised as a pain point on the Ansible forum and GitHub issues. (Source: Ansible Forum, GitHub issue #52759) - Role granularity debate: the community lacks consensus on whether roles should represent "server roles" (e.G.,
webserverrole that does everything) or "functions" (e.G., separatenginx,firewall,monitoringroles). (Source: Ansible Forum, Jul 2024) - Standalone vs collection confusion: the migration from standalone roles to collections creates uncertainty about which format to use for new roles. (Source: Adfinis Ansible Guide)
tests/directory ambiguity: generated byansible-galaxy initbut not counted among "seven main standard directories." In practice, most roles use Molecule (molecule/directory) instead of the built-intests/, creating a mismatch between the scaffold and actual testing practice. (Source: CMU SEI; molecule documentation)
Open questions
- Will Ansible eventually deprecate standalone roles in favor of collections-only distribution? The Adfinis guide notes there is no official announcement, but the direction is clear.
- How will the
extensions/directory in collections evolve? It's described as a placeholder for future features. - Will the Molecule project eventually replace or merge with the
tests/directory convention generated byansible-galaxy init?
Practical takeaways
- Start every new role with
ansible-galaxy role init <name>- it produces the correct scaffold and saves time. - For new projects, prefer collections over standalone role repos. Bundle related roles, modules, and plugins in one collection repository.
- Use
defaults/main.ymlfor everything users might override. Reservevars/main.ymlfor OS-specific constants loaded viainclude_varsorfirst_found. - Split tasks into separate files (e.G.,
install.yml,config.yml) and usemain.ymlonly as a router withimport_tasksand tag assignments. - Match target filesystem paths inside
files/andtemplates/directories for clarity. - Pin role/collection versions in
requirements.ymland use project-local paths viaansible.cfgfor reproducible deployments. - Use Molecule for testing instead of the basic
tests/directory generated byansible-galaxy init. - Namespace all role variables with the role name prefix to prevent collisions in multi-role projects.
Sources used
- Roles - Ansible Community Documentation - https://docs.ansible.com/projects/ansible/latest/playbook_guide/playbooks_reuse_roles.html
- Galaxy Developer Guide - Ansible Community Documentation - https://docs.ansible.com/projects/ansible/latest/galaxy/dev_guide.html
- Best Practices - Ansible Documentation - http://ansible-doc.readthedocs.io/en/latest/rst/playbooks_best_practices.html
- Collection structure - Ansible Community Documentation - https://docs.ansible.com/projects/ansible/latest/dev_guide/developing_collections_structure.html
- Role Directory Structure - Ansible for Network Automation - https://ansible-cbt-lab.readthedocs.io/en/latest/13_roles/02_structure.html
- Writing Ansible roles: best practices for strong roles - Sue B.V. - https://sue.nl/en/insights/knowledge/the-art-of-writing-ansible-roles/
- Ansible Roles Explained - Structure, Create & Best Practices - https://www.ansiblebyexample.com/articles/ansible-roles-explained-structure-best-practices
- Ansible Roles: Step-by-Step Guide (2025) - PyNet Labs - https://www.pynetlabs.com/ansible-roles/
- Ansible Roles Tutorial: Build Reusable Automation [2026] - ComputingForGeeks - https://computingforgeeks.com/ansible-roles-tutorial/
- How to Understand Ansible Role Directory Structure - OneUptime - https://oneuptime.com/blog/post/2026-02-21-how-to-understand-ansible-role-directory-structure/view
- Writing Ansible Roles with Confidence - CMU SEI - https://www.sei.cmu.edu/blog/writing-ansible-roles-with-confidence/
- Ansible: Tasks vs Roles vs Handlers - Roelof Jan Elsinga - https://roelofjanelsinga.com/articles/ansible-difference-between-tasks-and-roles/
- Roles & Collections - Adfinis Ansible Guide - https://docs.adfinis.com/ansible-guide/roles_collections.html
- Ansible best practices: project-local collections and roles - Jeff Geerling - https://www.jeffgeerling.com/blog/2020/ansible-best-practices-using-project-local-collections-and-roles
- Roles purpose, best practices - Ansible Forum - https://forum.ansible.com/t/roles-purpose-best-practices/7125