AnsibleXXXVII · Environments and Repository ArchitectureEnvironments and repository layout
Conventions, ownership and the 03:00 README
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
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.
| Path | A wrong change here means | Review needed |
|---|---|---|
inventories/production/ | The wrong machines get changed | Named owner, and a --list-hosts diff in the pull request |
inventories/*/group_vars/*/vault.yml | A credential is exposed or a run cannot decrypt | Security owner |
roles/<storage, network, boot>/ | A host does not come back | Domain owner, plus staging evidence |
roles/<application config>/ | A service misbehaves and is recoverable by rerunning | Ordinary review |
playbooks/ entry points | An operator runs something with a different scope than its name implies | Ordinary review, with attention to the hosts: line |
inventories/development/ | A developer has a bad afternoon | Ordinary review |
molecule/, scripts/, docs | CI is noisier or a helper breaks | Ordinary 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- Where the vault passwords come from, per environment, and who to ask if you do not have one. Not the passwords - the process.
- 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.
- 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
Q1. Why is CODEOWNERS with a single line matching * worse than having no CODEOWNERS at all?
Q2. Which lines belong in a README written for someone paged at 03:00? Select all that apply.
Q3. Prefixing every role variable with the role name is as much an incident-response measure as a correctness one.
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.