Lesson 9 of 9 · 24m
The 200-Playbook Codebase: Layout, Testing, and Conventions
The repo structure, ansible.cfg, tagging, testing with Molecule, linting, and the naming conventions that decide whether your Ansible ages into infrastructure or into archaeology.
Every team that adopts Ansible follows the same arc: one playbook, then ten, then a shared repo nobody can navigate. The ones that survive past playbook ~50 all converged on the same ideas — a predictable layout, entry points that mean something, automated testing of roles, and conventions enforced by lint instead of by memory. This lesson is the repo I'd hand a new hire.
The layout that scales
ansible/
ansible.cfg # forks, roles_path, vault_identity_list — in the repo
requirements.yml # pinned collections and external roles
site.yml # the entry point — converges an environment
inventories/
production/ { hosts.yml, group_vars/, host_vars/ }
staging/ { hosts.yml, group_vars/, host_vars/ }
playbooks/ # web.yml, db.yml, maintenance/ — one concern each
roles/ # common/, nginx/, postgres/ — project-ownedThe rule: ansible-playbook -i inventories/production site.yml converges the whole environment. Everything else is a subset you reach via -l or --tags. Environments differ by inventory, never by forked playbooks.
Tags and include discipline
# site.yml — tags on import_playbook apply to everything inside
- import_playbook: playbooks/common.yml
tags: [base]
- import_playbook: playbooks/web.yml
tags: [web]
- import_playbook: playbooks/db.yml
tags: [db]Tag at the import level, not on every task. A small, agreed tag vocabulary (base, web, db, deploy, certs) beats forty bespoke tags nobody can spell. --list-tasks --tags deploy should tell an operator exactly what a deploy touches.
Test roles, not playbooks
Playbooks orchestrate; roles hold logic — so test the roles. Molecule spins a container or VM, runs your role, and lets you assert:
# roles/postgres/molecule/default/molecule.yml
driver:
name: docker
platforms:
- name: pg-test
image: geerlingguy/docker-debian12-ansiblemolecule test # create → converge → idempotence → verify → destroy
molecule converge # iterate on the role without the full cycleThe idempotence step is the gift: it runs your role twice and fails if the second run reports changed. Half the "is this role honest?" questions answer themselves.
Conventions worth writing down
- FQCN everywhere, every task named —
ansible-lintenforces both; the log is the runbook. - Lint and dry-run in CI —
ansible-linton every PR plussite.yml --check --diffagainst staging, so reviewers see the delta, not the YAML. - One role = one concern —
nginxconfigures nginx; TLS certs are their own role. - No secrets outside vault; pin everything —
gitleaksin CI, pinned collection versions,ansible-coreinrequirements.txt.
Field notes
- Keep
ansible.cfgin the repo — team config beats "ask Dave what his forks setting is." (Ansible ignores it in world-writable dirs, so mind CI checkout permissions.) - Retire playbooks aggressively: a
playbooks/oneoff/folder of 2019 migrations is a trap someone will-l allone day. - The codebase is healthy when a new hire can predict where a change lives from the hostname it affects. If they can't, the layout's failing them — not the other way around.