skip to content

Roles and Reuse

The standard directory layout that turns a sprawling playbook into something shareable, plus Galaxy and collections for distribution. Interviewers ask about defaults versus vars and import versus include, because both decide when things get evaluated.

on this pageshow

questions

6

In an Ansible role created by `ansible-galaxy init`, what does each standard directory (tasks, handlers, defaults, vars, files, templates, meta) hold, and which files does Ansible load automatically?

level: juniorimportance: must knowfreq 72%

answer

  1. convention over configuration on disk
  2. every subdirectory is loaded by main.yml
  3. files/ and templates/ are search paths
  4. defaults low precedence, vars high
  5. meta holds dependencies plus Galaxy info

basics

~10 s

An Ansible role is a fixed directory tree - tasks/, handlers/, defaults/, vars/, files/, templates/, meta/. Ansible auto-loads main.yml from each of those directories, and resolves copy and template sources against files/ and templates/.

solid answer

~40 s

A role is a convention-over-configuration directory layout, and the convention is that Ansible looks for `main.yml` in each subdirectory. `tasks/main.yml` is the entry point that runs when the role is applied. `handlers/main.yml` defines the role's handlers. `defaults/main.yml` holds overridable default variables and `vars/main.yml` holds high-precedence role variables. `files/` holds static files that the `copy` module finds by bare filename, `templates/` holds Jinja2 templates that the `template` module finds the same way - both are searched relative to the role, which is why `src: nginx.conf` works with no path. `meta/main.yml` carries role dependencies and the Galaxy metadata (author, license, supported platforms). `ansible-galaxy init <name>` scaffolds all of it plus a README and a `tests/` directory, so you get the layout for free rather than remembering it.

code

bash · 2 lines
bash
ansible-galaxy init nginx
find nginx -maxdepth 2 -not -path '*/.git*' | sort

go deeper

for a junior

Be able to name the directories and say what each holds, and state that tasks/main.yml is the entry point Ansible runs. Mention that copy finds files in files/ and template finds them in templates/.

for a middle

Explain that the layout is a loading convention, not syntax: Ansible looks for main.yml in each known subdirectory and skips the ones that do not exist. Be ready to say how you split a large role across several task files.

for a senior

Show how the layout enables reuse in practice - a role with no absolute paths can be versioned, published and consumed unchanged by another team. Talk about what you keep out of a role to stop it becoming a second playbook.

for a principal

Own the standard: what a role in your estate must include before it is shareable (README, defaults for every knob, declared platforms, tests) and whether custom plugins ship inside roles or get promoted into a collection.

## What a role actually is A role is not a language feature with its own syntax - it is a **directory layout plus a loading convention**. When you apply a role, Ansible walks a known set of subdirectories and loads `main.yml` from each one it finds. Nothing else about the role is declared anywhere; the filesystem is the interface. That is why two roles written by two different teams still compose: they agree on where things live. ## The directories ``` nginx/ tasks/main.yml # the entry point - what the role does handlers/main.yml # handlers the role's tasks can notify defaults/main.yml # default variables, lowest precedence vars/main.yml # role variables, high precedence files/ # static files, copied verbatim templates/ # Jinja2 templates, rendered per host meta/main.yml # dependencies + Galaxy metadata README.md ``` **tasks/** - `tasks/main.yml` is the only automatic entry point. Larger roles split work into `install.yml`, `configure.yml` and so on, and `tasks/main.yml` pulls them in with `ansible.builtin.import_tasks` or `ansible.builtin.include_tasks`; inside a role those filenames resolve relative to `tasks/`, so `import_tasks: install.yml` is enough. Callers can also select an alternative entry point with the `tasks_from` option of `include_role`/`import_role`. **handlers/** - `handlers/main.yml` is loaded whenever the role is applied, so the role's own tasks (and, once the role has run, later tasks in the play) can notify them by name. **defaults/** and **vars/** - both are automatically loaded variable files, and the *only* difference that matters is precedence. Role defaults sit at the very bottom of Ansible's variable precedence ladder, just above command-line connection settings, so essentially anything a user sets wins over them. Role vars sit far higher - above inventory `group_vars`/`host_vars` and above play `vars` - and are beaten only by task-level vars, role parameters, `set_fact`, and `-e` extra vars. So `defaults/` is the role's public knob panel and `vars/` is its internal constants drawer. **files/** and **templates/** - these are search paths, not just folders. Inside a role, modules that take a `src` resolve it relative to the role: `ansible.builtin.copy` looks in `files/`, and `ansible.builtin.template` looks in `templates/`. This is what makes a role relocatable - no absolute paths anywhere: ```yaml - name: Install the config ansible.builtin.template: src: nginx.conf.j2 # found at nginx/templates/nginx.conf.j2 dest: /etc/nginx/nginx.conf ``` **meta/** - `meta/main.yml` carries two unrelated things: a `dependencies:` list of roles that must run before this one, and `galaxy_info` (author, description, license, `min_ansible_version`, supported `platforms`, `galaxy_tags`) which Galaxy reads when the role is published and which humans read to know whether the role targets their OS. Scaffolds from `ansible-galaxy init` also include a `tests/` directory with a throwaway inventory and playbook. Roles may additionally ship code in `library/` (custom modules), `module_utils/`, `filter_plugins/` and `lookup_plugins/`, though modern practice is to ship plugins in a collection instead. ## Why the layout is the interview question Because the layout *is* the reuse story. A playbook that has grown to eight hundred lines cannot be shared, versioned or tested; the same content split into roles can be published to Galaxy, pinned by version, and dropped into another team's repository without editing a path. Interviewers ask it as a screening question: if you cannot say what `defaults/` is for, you have used roles someone else wrote but never authored one. ## Common traps Creating a top-level `main.yml` in the role root does nothing - the loader only looks inside the known subdirectories. Putting a Jinja2 template in `files/` means `template` will not find it by bare name. Assuming every directory must exist is also wrong: a role with only `tasks/main.yml` is perfectly valid, and empty directories are simply skipped. Finally, `ansible-galaxy init` creates the skeleton but no content - it does not inspect anything or generate tasks for you.

  • If a role's work is too big for one file, how do you split tasks/main.yml without breaking the role?
    Add more task files under `tasks/` and pull them in from `tasks/main.yml` with `ansible.builtin.import_tasks: install.yml` or `include_tasks:`. Inside a role those names resolve relative to `tasks/`, so no paths are needed and the role stays relocatable. A caller who wants a different entry point can pass `tasks_from: configure.yml` to `include_role` or `import_role`.
  • Does every one of those directories have to exist for the role to work?
    No. Ansible simply skips subdirectories that are absent, so a role consisting of nothing but `tasks/main.yml` is valid and common. `ansible-galaxy init` creates the full skeleton for convenience, and many teams delete the empty parts before committing so readers are not misled into thinking there is content there.
  • What goes in meta/main.yml besides dependencies?
    The `galaxy_info` block: author, description, `license`, `min_ansible_version`, the `platforms` list of OS/version pairs the role supports, and `galaxy_tags`. Galaxy renders it on the role's page and uses it for search, and it is how a consumer decides whether the role targets their distribution before they install it.

saying these in an interview costs you the question

  • Says a role needs a main.yml at its root directory
  • Puts Jinja2 templates in files/ instead of templates/
  • Thinks ansible-galaxy init generates working tasks
  • Claims all seven directories are mandatory
  • Cannot name any difference between defaults/ and vars/

context

open as a page

When authoring an Ansible role, how do you decide whether a variable belongs in defaults/main.yml or vars/main.yml?

level: middleimportance: must knowfreq 62%

basics

~20 s

defaults/main.yml holds the role's overridable knobs: it is the lowest-precedence variable source, so almost anything a consumer sets wins. vars/main.yml sits far higher, so reserve it for role-internal constants the consumer should not have to change.

open as a page

What is an Ansible collection, how does it relate to roles, and what does a fully qualified name such as community.general.timezone refer to?

level: middleimportance: should knowfreq 50%

basics

~20 s

A collection is Ansible's packaging and distribution unit: a namespace.name bundle of modules, plugins, roles and playbooks. Content inside it is addressed by fully qualified collection name, such as community.general.timezone, and installed with ansible-galaxy collection install.

open as a page

In an Ansible playbook, what is the difference between import_role and include_role, and how does the choice affect tags, loops and when?

level: middleimportance: should knowfreq 58%

basics

~20 s

import_role is static: Ansible expands the role when it parses the playbook, so its tasks inherit tags and show up in --list-tasks, but it cannot be looped. include_role is dynamic: resolved at runtime, so it can be looped and skipped as one unit.

open as a page

Your Ansible playbooks depend on third-party Galaxy roles and collections. How do you make those dependencies reproducible in CI?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Declare every dependency in a version-controlled requirements.yml with an exact version, install it in CI with ansible-galaxy role install -r and ansible-galaxy collection install -r into a project-local path, and never rely on whatever happens to be on the control node.

open as a page

What does the dependencies list in an Ansible role's meta/main.yml do, and how does Ansible handle the same dependency being pulled in by several roles?

level: seniorimportance: nice to knowfreq 32%

basics

~20 s

The dependencies list in a role's meta/main.yml names roles Ansible applies before the role itself. Within one play a dependency runs only once for a given set of parameters, unless its own meta/main.yml sets allow_duplicates: true.

open as a page