Why a role library beats a folder of playbooks
Most estates that have been “automated” with Ansible look the same. One repository holds a dozen playbooks, each several hundred lines long, each written by a different person. Variables are defined in four places. Half the tasks use shell with a when guard that approximates idempotency. Nobody is confident enough to run anything against production without a long Slack thread first.
That is the gap between having Ansible and having a configuration management practice. A role library closes it. It turns tribal knowledge about how a web server, a database host or a bastion should be configured into versioned, reviewed, tested code. This page describes how we build that library as part of our wider Ansible and configuration management service.
What a good library looks like
Concrete criteria we hold ourselves to:
- One concern per role. An
nginxrole installs and configures nginx. It does not also create application users or open firewall ports for unrelated services. - Idempotent by construction. A second run reports
changed=0. Anything else is a bug, not a feature. - Safe defaults, explicit overrides.
defaults/main.ymlcontains values that are fine in any environment. Environment-specific values live ingroup_vars. - Prefixed variables. Every variable starts with the role name, such as
nginx_worker_connections, which prevents collisions and makes search trivial. - Fully qualified module names.
ansible.builtin.template, nottemplate, so the code is unambiguous when collections overlap. - Documented interfaces. Each role declares its inputs in
meta/argument_specs.yml, so a typo in a variable fails early with a clear message.
How it works in practice
Repository layout
For most clients we recommend one Git repository for roles and a separate one for inventories and playbooks. That lets roles be versioned and released independently of environments.
roles/
nginx/
defaults/main.yml
handlers/main.yml
meta/argument_specs.yml
tasks/main.yml
templates/nginx.conf.j2
playbooks/
web.yml
inventories/
production/
hosts.yml
group_vars/web.yml
requirements.yml
A role written for idempotency
The most common defect we find is shell commands standing in for modules. Compare the two approaches to a simple task:
# Fragile: runs every time, reports changed every time
- name: Set max open files
ansible.builtin.shell: echo "fs.file-max = 200000" >> /etc/sysctl.conf
# Idempotent: changes only when the value differs
- name: Set max open files
ansible.posix.sysctl:
name: fs.file-max
value: "200000"
state: present
sysctl_set: true
reload: true
The second form is declarative, supports check mode and gives honest change reporting. When no module exists, we use command with creates, removes or changed_when so the task is still safe to repeat.
Thin playbooks
A playbook should read like a table of contents:
- name: Configure web tier
hosts: web
become: true
roles:
- role: baseline
- role: nginx
vars:
nginx_worker_connections: 4096
- role: app_deploy
If you can understand what a server is meant to be by reading ten lines, the design is working.
Consuming roles
Roles are pinned in requirements.yml, so every environment installs the same version:
roles:
- name: nginx
src: [email protected]:your-org/ansible-role-nginx.git
scm: git
version: v1.4.0
Running ansible-galaxy install -r requirements.yml pulls exactly that tag. Promoting a new role version through dev, staging and production becomes a normal pull request.
Decisions and trade-offs
Write your own role or use a community one? Community roles save time for commodity software but bring their own variable names, supported platforms and upgrade pace. We often use a well-maintained community role as a starting point, read it fully, and fork it when your needs diverge. For security-sensitive pieces such as SSH configuration, we prefer roles we have read line by line.
One big role or several small ones? Small roles compose better and test faster. The price is more files and more pins. For anything shared across teams, small wins.
Roles inside the application repository or a central one? Central is easier to govern and test. Embedded is faster for a single team that owns one deployment. We pick based on how many teams touch the same servers.
When not to build this. If your fleet is mostly containers on Kubernetes with immutable images, a large role library is overkill. A small baseline for nodes and the occasional bastion may be all you need. Our Docker and Kubernetes teams will tell you honestly if that is your situation.
Common failure modes
- Variable sprawl. The same setting defined in role defaults, group vars, host vars and extra vars. We document precedence, keep defaults minimal and forbid host-level overrides without a comment.
- Shell as a crutch. Replaced with modules, or guarded properly.
- Hidden role dependencies. Role A silently assumes role B ran first. We declare dependencies in
meta/main.ymlor restructure. - Untested templates. A Jinja2 edit that breaks a config file only on one distribution. Covered by Molecule tests.
- Secrets in variables. Handled through Vault integration rather than plaintext.
Delivery and handover
We work in your repositories from day one, so there is no big-bang transfer at the end. Every role arrives through a pull request your engineers review. By the end you have the library, CI that lints and tests it, a role catalogue and contribution guide, and the knowledge to add the next role without us. If you want to run playbooks from a web interface with approvals, the AWX deployment builds directly on this library.
A note on ownership
Every role in the library should name an owner or owning team in its metadata. Unowned roles drift, collect unreviewed changes and become the ones nobody dares touch, so we assign owners during handover.
How to tell it is working
- A routine run against a stable server reports zero changes.
- A new engineer can find the role for a task in minutes and change it through a pull request.
- Building a new server of a known type is a single playbook run with no manual steps.
- Role versions are pinned, and production runs a version that has already passed through staging.
- Review comments focus on design, not on whether the playbook will work.
If your automation is a pile of scripts today, a short review will show which parts to keep. Contact us to discuss your estate.