Automation
Ansible in production: structure an operations repository before exposing it in AWX
Organize an Ansible repository used by AWX with bounded playbooks, reusable roles, separated inventories, readable variables, versioned collections and operations documentation.
AWX does not magically make an Ansible repository clean. It mainly makes its content more visible, easier to launch and sometimes easier to misuse. Before exposing playbooks through an interface, structure the repository as an operations tool: clear scopes, separated inventories, reusable roles, understandable variables and minimal documentation.
The scenario is simple: a team manages a Linux estate with a few playbooks already stored in Git. Runs still happen from administrator workstations. AWX should centralize recurring actions, not become the place where a missing repository structure is improvised.
Separate playbooks, roles and inventories
An operable repository should make it clear what is executable, what is reusable and what describes targets. Playbooks exposed in AWX should be few and action-oriented. Roles hold reusable logic. Inventories describe environments and groups.
ansible-operations/
playbooks/
check-linux-baseline.yml
check-disk-usage.yml
restart-approved-service.yml
patch-selected-package.yml
apply-linux-baseline.yml
roles/
linux_baseline/
ssh_hardening/
package_management/
monitoring_agent/
inventories/
preprod/hosts.yml
production/hosts.yml
group_vars/
all.yml
production_web.yml
production_tools.yml
collections/requirements.yml
README.md This structure is not sophisticated, but it makes review possible. An AWX template points to an identifiable playbook. The playbook calls a role. The inventory limits the target. Variables are versioned.
Keep exposed playbooks short
A playbook used as an AWX job should be a controlled entry point. It should not contain all technical logic. It prepares context, validates parameters and calls the required roles. This reduces duplication and makes behavior easier to review.
---
- name: Apply Linux baseline
hosts: "{{ target_group }}"
become: true
gather_facts: true
pre_tasks:
- name: Reject restricted groups
ansible.builtin.fail:
msg: "This playbook cannot target restricted hosts"
when: target_group in ['restricted', 'production_databases']
roles:
- role: linux_baseline
- role: monitoring_agent
post_tasks:
- name: Show kernel and distribution
ansible.builtin.debug:
msg: "{{ inventory_hostname }} {{ ansible_distribution }} {{ ansible_distribution_version }} kernel {{ ansible_kernel }}" The role can evolve with its defaults and tests. The playbook remains readable for the team approving the AWX template.
Name inventories to prevent mistakes
Inventories should be explicit. A single hosts.yml file often ends up containing too many groups and too many special cases. Separating preproduction and production already avoids a class of errors. Separating sensitive groups prevents standard jobs from touching them accidentally.
all:
children:
production_web:
hosts:
web01.example.local:
web02.example.local:
production_tools:
hosts:
tools01.example.local:
production_databases:
hosts:
db01.example.local:
restricted:
children:
production_databases: In AWX, a maintenance template should point to the expected inventory. If an inventory can be selected at launch time, there should be a strong reason and playbook-side validation.
Version collections
A playbook that works today can change behavior after a collection update. For stable operations, collections must be declared and installed predictably. AWX can use execution environments, but the repository should still state its dependencies.
---
collections:
- name: ansible.posix
version: "1.5.4"
- name: community.general
version: "8.6.0" The exact versions depend on context, but the intent is clear: AWX execution should not depend on an implicit environment nobody can rebuild.
Document published jobs
The README does not need to be a full manual. It should describe exposed playbooks, their effects and limits. That lets a reviewer understand what an AWX button really does.
Job: check-disk-usage
Effect: read-only
Targets: preprod, production_web, production_tools
Credential: read-only
Job: restart-approved-service
Effect: restarts nginx, telegraf or chronyd
Targets: production_web, production_tools
Credential: maintenance
Validation: service_facts after restart
Job: apply-linux-baseline
Effect: applies linux_baseline and monitoring_agent roles
Targets: preproduction by default, production after approved change
Credential: restricted-admin Plan AWX branches
A repository can be clean but risky if AWX tracks an unstable branch. A simple practice is to expose production from a stable branch or tag, while keeping work branches for tests. The exact Git workflow depends on the team, but it must be deliberate.
main
Review and integration of approved changes
production
Branch followed by production AWX templates
feature/*
Development and tests on a separate AWX project
Tags
Possible rollback points for sensitive changes Conclusion
Structuring an Ansible repository before AWX is not cosmetic. It is an operations requirement. Exposed playbooks must be short, roles reusable, inventories explicit, dependencies versioned and effects documented.
AWX then becomes a controlled executor of versioned code. Without that foundation, it becomes an interface that gives a sense of governance to automations that are still too free. The right order is simple: make the repository readable, bound the actions, test the roles, then publish the templates.