Growing Beyond One File
Scenario: site.yml has reached 900 lines. Two engineers cannot work on it at once without conflicts, and nobody can find the database section.
New words, in plain English
- Entry-point playbook - the single file you actually run, usually
site.yml. - import_playbook - pulls another playbook file in at parse time.
- Role - a standard directory layout that packages tasks, handlers, templates, files and defaults as one reusable unit.
- Tag - a label on a task or play, so you can run just part of a playbook with
--tags.
Playbooks grow, and there are three separate axes along which to split them.
By machine role - webservers.yml, dbservers.yml - pulled together by a small site.yml using import_playbook. This keeps ownership clear.
By reusable capability - roles. A role is a directory with a fixed layout that Ansible knows how to load, and it is the unit teams actually share. Phase 3 covers this properly.
By operation - tags. Tags do not split files; they let one file be run partially. --tags config during a change freeze, --skip-tags packages when the package mirror is down. Phase 4 covers this.
The mistake worth avoiding early is splitting purely by size. A 900-line file is a symptom; the cure is finding the real boundaries, not cutting it into three 300-line files with no meaning.
Analogy: Splitting a long book: chapters by topic (roles), volumes by audience (per-tier playbooks), and an index that lets a reader jump straight to one section (tags). Tearing the pages into equal-sized bundles helps nobody.
A worked example
# site.yml - the entry point that composes everything
- import_playbook: playbooks/webservers.yml
- import_playbook: playbooks/dbservers.yml
- import_playbook: playbooks/loadbalancers.yml
# playbooks/webservers.yml
- name: Configure the web tier
hosts: webservers
roles:
- common
- webapp
# ansible-project/
# ├── site.yml
# ├── playbooks/
# ├── inventory/
# ├── group_vars/
# ├── host_vars/
# └── roles/
# ├── common/
# └── webapp/
One entry point matters more than it sounds. When there is exactly one file that runs everything, CI has an obvious target, onboarding has an obvious starting page, and "what does this repository do?" has a one-file answer. Multiple competing entry points are how estates end up with automation nobody dares to run.
Keep the composition layer thin. site.yml should read like a table of contents - imports and nothing else. Logic belongs in the roles and playbooks it composes.
Tip: If site.yml contains any task at all, it has stopped being a table of contents. Move that task into a role or a per-tier playbook.
Goal: Put this to work in the ansible-tags-partial-run lab. Open/labs/ansible, pickansible-tags-partial-run, and fix the real broken project - Ansible really does SSH into four managed hosts and converge them.