Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXXVII · Infrastructure Repository ArchitectureRepoArch

Ansible repository layout — roles, playbooks, inventories, and group_vars

Advanced⏱ ~24 mingit

What you'll learn

  • Identify the role-based layout of an Ansible repository (roles, playbooks, inventories, group_vars)
  • State what belongs in a role versus a top-level playbook
  • Apply Galaxy best practices to a role directory structure (tasks, handlers, defaults, vars, meta)
  • Recognise why inventories and group_vars live at the repository root, not inside roles

Prerequisites

Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x

Not yet marked complete on this device.

The Ansible repository layout is role-based because Ansible’s unit of reuse is the role: a self-contained directory of tasks, handlers, templates, defaults, and vars that applies to any host group. The four top-level directories organise the reusable content, the entry-point execution, and the host-side truth.

The role-based layout

flowchart TB
    R["infra-ansible/ (root)"]
    R --> RL["roles/"]
    R --> PB["playbooks/"]
    R --> IN["inventories/"]
    R --> GV["group_vars/"]
    RL --> RA["roles/common/"]
    RL --> RB["roles/webserver/"]
    IN --> IA["inventories/prod/"]
    IN --> IB["inventories/staging/"]
    PB --> PA["playbooks/site.yml"]

The four primary top-level directories:

  • roles/ holds reusable units of automation (webserver, database, common-base). Roles are what playbooks call and what gets shipped to Galaxy.
  • playbooks/ holds the entry-point playbooks. A playbook is the unit of execution; a role is the unit of reusable content.
  • inventories/ holds the host lists, with a separate directory per environment so secrets and hostnames stay separate.
  • group_vars/ holds per-group variables: group_vars/all.yml applies to every host; group_vars/web.yml applies to every host in the web group.

What goes inside a role

A role’s directory follows the Galaxy standard layout:

roles/webserver/
  tasks/main.yml
  handlers/main.yml
  defaults/main.yml
  vars/main.yml
  files/
  templates/
  meta/main.yml
  README.md
  • tasks/main.yml is the only file the role needs to be callable. Every other directory is optional.
  • defaults/main.yml has the lowest variable precedence; it is the role’s public interface. vars/main.yml has the highest internal precedence; the caller cannot override it.
  • handlers/main.yml holds handlers (notified tasks). meta/main.yml declares role dependencies and Galaxy metadata.

What goes at the repository root

The repository root holds what applies to every role and every playbook:

  • ansible.cfg - the project’s Ansible configuration. The repo pin to roles/, the inventory, SSH settings, and the stdout callback.
  • requirements.yml - the list of Galaxy roles and collections.
  • A top-level README.md describing purpose, environments, and standard ansible-playbook invocations.
  • A .gitignore excluding *.retry, *.pyc, __pycache__/, and any cleartext vault-encrypted variable files.
  • A collections/ directory and a vault-password-file reference (the file itself is not in the repository).

Inventories and group_vars

Inventories and group_vars are deliberately at the repository root, not inside any role:

  • An inventory file is per environment (inventories/prod/hosts, inventories/staging/hosts).
  • A group_vars/ directory holds variables for those groups; group_vars/web.yml loads for every host in the web group.

This split is what makes a role portable: the role declares its inputs through defaults/main.yml; the inventory and group_vars/ supply the values. The same role applies to dev, staging, and prod without modification.

Cross-repo consumption

When a role is consumed by another repository, the consumer lists the role in its own requirements.yml:

roles:
  - name: webserver
    src: https://github.com/example/infra-ansible.git
    scm: git
    version: main
    path: roles/

The consumer’s ansible-galaxy install -r requirements.yml clones the role into its own roles/webserver/ and pins it to the specified ref. Collections follow the same pattern.

Production discipline

  1. Use the role-based layout from the first playbook. A flat site.yml with hundreds of tasks is technical debt.
  2. One role per coherent unit of configuration. The boundary should be the boundary a new engineer would draw on a whiteboard.
  3. Keep defaults/main.yml and vars/main.yml apart. Defaults are the public interface; vars are internal constants.
  4. Per-environment inventory, not per-environment playbook. If prod and staging playbooks differ only in variables, the layout is wrong.
  5. Pin every Galaxy role and every collection in requirements.yml. A latest ref resolves to whatever the upstream pushed most recently.

Cross-course references

  • Ansible for Production Sysadmins - Part IX (Roles) covers the role-call mechanics this layout assumes.
  • Linux for Production Sysadmins - Part XXVI (RepoLayout) covers the filesystem analogue: role, data, and configuration directories in /etc.
  • Git Internals for Production Engineers - Part XXXI covers the cross-repo pattern when a role is consumed by a playbook in another repository.

Quiz

Knowledge check · 4 questions

  1. Q1. An Ansible repository has a `roles/webserver/` role and a `playbooks/site.yml` playbook. Where should the per-environment host list live?

  2. Q2. A `defaults/main.yml` in a role has lower precedence than a `vars/main.yml` in the same role, so values in `vars/main.yml` override values in `defaults/main.yml`.

  3. Q3. Name the four primary top-level directories in a role-based Ansible repository layout, and state the role of each one.

  4. Q4. Recommend the right Ansible layout for a team that has three roles (webserver, database, common-base), two environments (prod, staging), and one application playbook, and identify the file that the application playbook's repository needs in order to consume the webserver role.

    A platform team maintains an Ansible repository with a `webserver` role, a `database` role, and a `common` role. An application team maintains a separate repository with an `application.yml` playbook that needs to apply the webserver role to its hosts. The application team does not want to copy the role; they want to consume a pinned version.

Passing score: 75%. Answers are checked in this browser.