unit 9 · Workplace ecosystem

Repo structure & conventions

Thirty-five lessons, one tree

essential · 7 min · linux + windows

you may want to read 24 · Roles: structure, defaults vs vars , 26 · Galaxy, requirements.yml & pinning first — but nothing is locked

tl;dr

  • The standard repo: ansible.cfg at root, inventories/<env>/, site.yml, roles/, two requirements.yml.
  • One inventory directory per environment makes cross-env accidents physically impossible.
  • Day one in a strange repo, read: ansible.cfg → inventories/ → site.yml → roles/.

why it matters Your first week is reading someone else’s repo, not writing your own — knowing the standard layout means you navigate it in minutes instead of grepping blind.

Thirty-five lessons in, you have met every file in a real Ansible repo — just never in one place. Lesson 6 gave you the project-root ansible.cfg. Lessons 8–10 built the inventory directories. Lesson 12 wrote the playbook. Lesson 24 filled roles/. Lesson 26 pinned dependencies. This lesson adds nothing new. It assembles:

project/
├── ansible.cfg               # lesson 6  — found first, wires the rest
├── site.yml                  # lesson 12 — the top-level playbook
├── playbooks/                # optional extra playbooks site.yml pulls in
├── inventories/
│   ├── staging/
│   │   ├── hosts.yml         # lesson 8  — hosts and groups
│   │   ├── group_vars/       # lesson 9  — values live with the env
│   │   └── host_vars/
│   └── production/           # lesson 10 — same shape, different machines
├── roles/
│   ├── requirements.yml      # lesson 26 — external roles, pinned
│   └── nginx/                # lesson 24 — tasks/, defaults/, handlers/…
└── collections/
    └── requirements.yml      # lesson 26 — collections, pinned
the standard layout — every line is a lesson you already took

The config at the top makes this a project. Lesson 6’s cwd slot beats home and machine-wide config, so any run from the repo root picks up:

[defaults]
inventory = inventories/staging/  # default — -i inventories/production/ overrides
roles_path = roles                # this repo's roles resolve first
ansible.cfg — why 'just run it from the repo root' works

That’s the whole trick. Clone, ansible-galaxy install -r both requirements files, run from the root. Every path resolves the same way for every colleague and for CI.

Four conventions reviewers actually enforce

One inventory directory per environment. Lesson 10’s rule: the environment is the directory you point -i at. A staging run cannot touch a prod host it cannot see — separation by construction, not by discipline.

Keep group_vars/all.yml minimal. Anything environment-specific belongs inside that environment’s directory. The shared all.yml is for genuinely universal values. In review, every addition to it gets challenged.

One role, one concern. A role named nginx installs and configures nginx. The moment it also creates deploy users and opens firewall ports, it stops being reusable. Split it.

Prefix role variables with the role name. Lesson 24 gave you the rule: nginx_listen_port, never listen_port. Lesson 16’s ladder is the reason. Variables from every role in a play land in one flat namespace. Two roles sharing a bare name silently fight, and precedence, not intent, picks the winner.

Day one, unfamiliar repo

Week one in your new team’s repo, read in this order:

  1. ansible.cfg — the map. Which inventory is default, where roles and collections resolve, what else is non-standard.
  2. inventories/ — the world. Which environments exist, which hosts are in them, which values differ.
  3. site.yml — the plot. What runs against which hosts, in what order. Follow its imports into playbooks/.
  4. roles/ — the machinery. One role at a time, defaults/main.yml first — lesson 24’s habit — to learn each role’s knobs before its tasks.

Half an hour of that beats a day of grepping. The layout is shared culture: the same tree in your repo, in Galaxy roles, in every colleague’s head. A tool enforces some of these conventions too, and your first PR will argue with it. Next up: ansible-lint.

at work, this sounds like

“Why is that in group_vars/all?”
The file every environment reads should stay minimal — an environment-specific value there is a review flag; it belongs in that env’s inventory directory.
“One role, one job — split it.”
Roles stay focused on a single concern; a role that installs nginx AND manages users AND opens firewall ports gets broken up in review.

self-check — recall, not a test

  1. 1 Day one in an unfamiliar repo: what four things do you read, in what order, and what does each tell you?

  2. 2 Why one inventory directory per environment instead of one inventory with prod and staging groups?

  3. 3 A value differs between staging and production. Where does it live, and why not group_vars/all.yml?

next up 37 · ansible-lint & yamllint 6 min · unit 9