Skip to main content
RunBook Academy

AnsibleXXXVII · Environments and Repository ArchitectureEnvironments and repository layout

Conventions, ownership and the 03:00 README

Intermediate⏱ ~18 minansible-coregit

What you'll learn

  • Adopt naming conventions that let grep answer ownership and blast-radius questions
  • Scope CODEOWNERS to the paths whose breakage causes an outage rather than to everything
  • Classify repository paths by how much damage a wrong change to them can do
  • Write a README that is usable by someone paged at 03:00 who has never seen the repository

Prerequisites

Verified against ansible-core 2.21.x · ansible (community package) 14.x · Python (controller) 3.12+ · ansible-lint 26.x · Molecule 26.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-11

Not yet marked complete on this device.

The previous six lessons built a repository. This one is about the parts of it that are not YAML: what things are called, who has to approve a change to which path, and the document somebody reads when a page has just woken them up.

None of it is enforced by Ansible. All of it decides how the repository behaves during the twenty minutes when it matters most.

Naming so that grep is an investigation tool

The most common question during an incident is what else touches this. The tool available is text search. Naming decides whether text search gives a useful answer.

Prefix every role variable with the role name. webserver_worker_processes, not worker_processes. Part XIII makes the correctness argument — the namespace prevents collisions between roles. The operational argument is different and just as strong: grep -r webserver_ . returns the complete surface of that role’s interface, across every environment, in one command. An unprefixed worker_processes returns that plus every coincidence.

Name groups after what the host does, not after what it is. webservers, postgres_primaries, edge_proxies. Not group1, not rack4, not bobs_boxes. A group name appears in the hosts: line of a play, which means it is the most-read blast-radius statement in the repository, and it should be readable by someone who has never met the estate.

Make dangerous playbooks look dangerous. A file named playbooks/rebuild-cluster.yml is treated more carefully than one named playbooks/maintenance.yml that happens to rebuild the cluster. The name is the first and often only warning an operator gets.

Reserve a prefix for files that are not entry points. The reference layout uses a leading underscore and a tasks/ subdirectory. Pick one and apply it without exception, because a convention that holds ninety per cent of the time is worse than none: it teaches people to trust it.

CODEOWNERS scoped to what can hurt

A CODEOWNERS file maps paths to reviewers whose approval is required before a change to those paths can merge. The failure mode is applying it to everything:

# CODEOWNERS — the version that does not work
*    @platform-team

That is not a control, it is a queue. Every typo fix now waits on the same group, the group learns to approve without reading, and the approval on the change that mattered means exactly as much as the approval on the whitespace fix.

Scope it to the paths where a wrong change is an outage:

# CODEOWNERS
# Production targeting. A change here decides which machines get changed.
/inventories/production/          @platform-oncall @sre-leads

# Vault material. A change here can expose or lock out a credential.
/inventories/*/group_vars/*/vault.yml    @security-team

# Roles that manage storage, the network, or the boot path.
/roles/lvm/                       @storage-owners
/roles/firewall/                  @network-owners
/roles/bootloader/                @platform-oncall

# The pipeline definition and the gates. Changing these changes what CI proves.
/.github/                         @platform-oncall
/.ansible-lint                    @platform-oncall

# Everything else: ordinary review by anyone on the team.

Two things to notice.

inventories/production/ is owned. Most wrong-target incidents are inventory changes, not role changes, and inventory changes look small — a host moved between groups is a two-line diff that can double a blast radius.

The CI configuration is owned. A change that weakens a gate is the quietest way to make the repository less safe, and it will otherwise be reviewed by whoever is free, as a build fix.

Classifying paths by damage

Write this table down once and put it in the README. It tells a new engineer where they can work unsupervised, which is a question they will otherwise answer by guessing.

PathA wrong change here meansReview needed
inventories/production/The wrong machines get changedNamed owner, and a --list-hosts diff in the pull request
inventories/*/group_vars/*/vault.ymlA credential is exposed or a run cannot decryptSecurity owner
roles/<storage, network, boot>/A host does not come backDomain owner, plus staging evidence
roles/<application config>/A service misbehaves and is recoverable by rerunningOrdinary review
playbooks/ entry pointsAn operator runs something with a different scope than its name impliesOrdinary review, with attention to the hosts: line
inventories/development/A developer has a bad afternoonOrdinary review
molecule/, scripts/, docsCI is noisier or a helper breaksOrdinary review

The purpose of the table is not bureaucracy. It is to make the default path — ordinary review, merge, get on with it — obviously safe, so that the small number of paths needing more attention actually get it.

The 03:00 README

Assume the reader has been woken up, has never opened this repository, and has ten minutes. Everything below is something they will need and cannot derive.

  1. What this repository controls, in two sentences. Which estate, which services, and explicitly what it does NOT control - the systems somebody might reasonably assume are in here and are not.
  2. The exact command to see what would change, per environment, with the vault id spelled out. Copy-pasteable. This is the first thing anyone should run and it should not require reading further.
  3. The expected host count per environment. A number they can compare against --list-hosts. This is the single cheapest detector of the wrong-inventory mistake and it is useless unless it is written down.
  4. Which playbooks are safe to rerun at any time, and which are not. Rerun safety is the property an operator most needs at 03:00 and is the hardest to infer from the code.
  5. How to limit a run to one host, with a real example from this estate. Not the generic --limit syntax - the actual command with a real hostname from this inventory.
  6. What runs on a schedule, when, and how to stop it. An operator debugging a change needs to know that something else will overwrite it in forty minutes.
  7. Where the vault passwords come from, per environment, and who to ask if you do not have one. Not the passwords - the process.
  8. Who owns which roles, and who is the escalation for the estate as a whole. Names and a rota link, not a team alias that pages nobody at night.
  9. The three most recent incidents involving this repository and what changed as a result. This is the section people skip writing and the one that most often prevents a repeat.

Knowledge check

Knowledge check · 4 questions

  1. Q1. Why is CODEOWNERS with a single line matching * worse than having no CODEOWNERS at all?

  2. Q2. Which lines belong in a README written for someone paged at 03:00? Select all that apply.

  3. Q3. Prefixing every role variable with the role name is as much an incident-response measure as a correctness one.

  4. Q4. You have inherited a large repository with broken conventions, no useful README and a CODEOWNERS matching everything. What should the first pull request do?

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