Chapter 8: Roles & Reuse
Topic 47

Role Dependencies and meta

Rolesmeta

meta/main.yml carries a role's metadata — its Galaxy info, its supported platforms, and its dependencies on other roles. Declaring dependencies: makes Ansible run those roles before this one, every time, which sounds convenient and is the source of the most tangled role graphs in practice. Explicit composition in a playbook almost always beats a buried dependency chain, and knowing the few cases where role dependencies earn their keep is the point of this topic.

The trap is that a dependency is invisible at the call site. A playbook that lists three roles can run seven, and nothing in the file the reader opens says so. That hidden ordering is fine for one genuinely-inseparable prerequisite and a liability the moment it becomes a habit.

The meta/main.yml File

meta/main.yml holds two things: galaxy_info and dependencies. galaxy_info records the author, license, minimum Ansible version, and supported platforms — Galaxy reads it to index and validate the role. The platforms listed there are documentation plus a soft gate, not enforcement: they describe where the role is meant to run, but nothing stops Ansible from trying the role on an unlisted OS and failing partway through. The dependencies list is the other half, and the one with teeth.

meta/main.yml — galaxy_info and a single justified dependency
# roles/larkspur_web/meta/main.yml
galaxy_info:
  author: larkspur-platform
  description: nginx + gunicorn for the Larkspur app tier
  license: MIT
  min_ansible_version: "2.15"
  platforms:
    - name: Ubuntu
      versions: [jammy, noble]
dependencies: []   # empty on purpose — compose in the playbook instead

An empty dependencies: [] here is a deliberate choice, not an oversight. The role declares what it is and where it runs, and leaves the question of what runs before it to the playbook that applies it.

How Dependencies Run

A role listed under dependencies: runs before the dependent role — once per play by default, with its variables resolved at parse time. So if larkspur_web depended on a base role, base would run first, automatically, whenever larkspur_web is applied. The ordering is implicit, which is exactly the problem: a reader of the playbook cannot see it. The dependency lives in the role's meta, not in the play, so the execution order is a fact you only learn by opening every role's metadata.

galaxy_info and Discoverability

For a role you intend to publish, galaxy_info is what makes it findable and usable. Author, description, license, min_ansible_version, and platforms are the fields Galaxy indexes and the fields an adopter checks before trusting your role. An empty or wrong galaxy_info is why a published role looks abandoned even when it works perfectly — no license means nobody can legally use it, no description means it never surfaces in a search, and no platforms means an adopter cannot tell whether it runs on their OS.

Dependency Chains as a Smell

A role that pulls in five other roles transitively creates an ordering nobody declared and a failure that points at the wrong file. When the third hidden role fails, the traceback names that role, but the playbook the engineer is reading lists only the top-level one — so the search starts in the wrong place. Heavy dependency trees are usually the wrong tool for "I need a database configured first." That is a sequencing need, and sequencing belongs in the playbook where it is visible, not buried in meta where each role silently assumes the previous one already ran.

Composition Over Dependency

Listing roles in the play's roles: block — or in sequential import_role calls — makes order explicit and visible. The reader opens the playbook and sees exactly what runs and in what sequence, with no metadata archaeology. Reserve meta dependencies for genuinely inseparable prerequisites: a role that is useless without its base role, where running one without the other never makes sense. For everything else — deploy order, "configure the database first" — compose in the play.

Explicit composition — order visible in the file you open
# site.yml — the reader sees the full order here
- hosts: app_servers
  become: true
  roles:
    - larkspur_db      # runs first — database tier
    - larkspur_web     # then the web tier that needs it

Nothing about this order is hidden — both roles are named in the play, and they run top to bottom. Compare that to encoding larkspur_db as a meta dependency of larkspur_web: the behavior would be identical, but the playbook would show one role and run two, and the next engineer would have no way to know.

Common Mistakes
  • Encoding deploy order in meta dependencies so the playbook shows three roles but seven actually run, and a failure in the second hidden role points the reader at the wrong place.
  • Relying on meta platforms: to stop a role running on the wrong OS — it is metadata and a soft check, not a hard gate, so the role still tries and fails mid-run on an unlisted distribution.
  • Letting a dependency run once per play when you needed it per host or per invocation, then debugging why the prerequisite "did not run the second time" it was referenced.
  • Building a five-deep dependency chain where each role assumes the previous one's variables, so changing one default cascades into failures three roles away from where you edited.
  • Shipping a role with empty galaxy_info — no license, no description — making it un-vettable and un-indexable on Galaxy, so it looks abandoned even though it works.
Best Practices
  • Compose roles explicitly in the play's roles: list or sequential import_role calls so execution order is visible in the file the reader opens.
  • Reserve meta dependencies: for prerequisites a role is genuinely useless without, not for ordinary deploy sequencing that belongs in the play.
  • Fill galaxy_info completely — author, license, description, min_ansible_version, supported platforms — so the role is discoverable and legally usable.
  • Enforce real OS gates with an explicit assert or when on ansible_facts, since meta platforms: documents intent but does not stop a wrong run.
  • Keep dependencies: [] empty by default and add to it only when you can name the prerequisite the role cannot function without.
Comparable tools Terraform module composition — calling modules from a root module, explicit over implicit Puppet module dependencies · metadata.json the direct twin Chef cookbook depends in metadata.rb — same mechanism, same smell at scale

Knowledge Check

A role lists another role under dependencies: in its meta/main.yml. When does the dependency run?

  • Before the dependent role, once per play by default, with its variables resolved at parse time
  • After the dependent role finishes all its tasks, purely as a cleanup step
  • Only if the dependent role explicitly calls include_role on it somewhere in its own task list during the run
  • Once per task in the dependent role, re-running fresh for every single task

Why are heavy meta dependency chains discouraged in favor of explicit composition?

  • The execution order is hidden in role metadata, so a failure in a transitive role points the reader at a playbook that does not list it
  • Dependencies declared in meta always run measurably slower than roles listed directly in the play
  • Ansible hard-caps each role at exactly one declared dependency, so any longer chain silently drops every role listed past the very first one
  • Dependency roles cannot use handlers or templates of their own, sharply limiting what they can do

You list Ubuntu under platforms: in meta/main.yml and run the role on RHEL anyway. What happens?

  • platforms is documentation and a soft check, not enforcement — the role still tries to run and fails partway through on the wrong OS
  • Ansible refuses to start the role and exits cleanly before any of its tasks run on the host
  • The role automatically translates its package names and module calls to the matching RHEL equivalents
  • Ansible pauses and prompts you interactively to confirm the override before it will continue running the role on an unlisted target platform

What is galaxy_info in meta/main.yml for?

  • It records author, license, description, minimum Ansible version, and platforms so Galaxy can index and an adopter can vet the role
  • It declares every input variable the role accepts and validates each of their types at parse time
  • It lists every handler the role notifies, exposing them by name so that other roles running in the same play can trigger them directly
  • It sets the precedence level of the role's defaults relative to other variables in the play

You got correct