The Ansible Role Structure Builder scaffolds a complete role: the standard directory tree plus ready-to-edit main.yml stubs for tasks, defaults, vars, handlers and meta. It mirrors what ansible-galaxy role init generates, so your role is instantly recognisable to anyone who works with Ansible.
How it works
An Ansible role is a fixed set of directories that the engine auto-loads by convention. tasks/main.yml is the entry point. defaults/main.yml and vars/main.yml hold variables at low and high precedence respectively. handlers/main.yml defines tasks that run only when notified — typically a service restart after a config change. meta/main.yml carries Galaxy metadata and role dependencies. Optional files/ and templates/ folders hold static assets and Jinja2 templates.
The tool normalises your role name to the snake_case form Galaxy expects, then emits each file pre-populated with a realistic example task, a notify/handler pair and platform metadata you can edit down to your real values.
Directory layout
my_role/
├── tasks/
│ └── main.yml ← entry point; include_tasks for larger roles
├── defaults/
│ └── main.yml ← low-precedence defaults (safe to override)
├── vars/
│ └── main.yml ← high-precedence internal constants
├── handlers/
│ └── main.yml ← tasks triggered by notify:
├── meta/
│ └── main.yml ← Galaxy metadata + role dependencies
├── files/ ← static files copied with copy: module
│ └── .gitkeep
└── templates/ ← Jinja2 templates used with template: module
└── .gitkeep
Handler wiring example
# tasks/main.yml
- name: Install required package
ansible.builtin.package:
name: "{{ my_role_package }}"
state: present
notify: Restart service
# handlers/main.yml
- name: Restart service
ansible.builtin.service:
name: example
state: restarted
Ansible deduplicates notifications: if ten tasks all notify: Restart service, the handler runs exactly once, at the end of the play. This is a key reason to use handlers rather than a direct service task for restarts.
defaults vs vars — when to use each
| File | Precedence | Use for |
|---|---|---|
defaults/main.yml | Lowest | Values the role consumer is expected to override — package names, ports, feature flags |
vars/main.yml | High | Internal constants the role should own — internal paths, computed values, things callers should not change |
A common mistake is putting everything in vars/, which makes the role hard to configure without editing it directly. Defaults are part of the role’s public interface; vars are its implementation details.
Tips and notes
- Keep overridable settings in
defaults/and internal constants invars/. - Drop a
.gitkeepinto anyfiles/ortemplates/folder you want in version control — Git does not track empty directories. - Split large task lists using
include_tasks:rather than growingtasks/main.ymlinto hundreds of lines. - Run
ansible-lintagainst the generated role before publishing to catch deprecated module names and missingname:keys.