From ansible
Use this skill whenever the user wants to create, modify, debug, or optimise Ansible playbooks, roles, inventories, or configuration. Triggers include any mention of 'ansible', 'playbook', 'role', 'inventory', 'host_vars', 'group_vars', Jinja2 templates for Ansible, or requests involving system configuration, package management, service orchestration, or VM provisioning. Also use when the user asks to scaffold a new role, review an existing playbook, fix a failed play, or write handlers/templates. If the user mentions 'ansible-navigator', 'ansible-vault', or their infrastructure layer patterns (layer1, layer2, etc.), use this skill. Do NOT use for AWX/Tower-specific configuration (job templates, workflows, credentials) — that is a separate skill.
How this skill is triggered — by the user, by Claude, or both
Slash command
/ansible:playbookThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Write, modify, debug, and optimise Ansible content.
Write, modify, debug, and optimise Ansible content.
Before writing any Ansible content, read the relevant reference files:
references/role-reference.rst — Role scaffolding patterns and directory conventionsThe user's project should have these installed (via pip, pipx, pixi, or devcontainer):
| Package | Version | Purpose |
|---|---|---|
ansible-core | >= 2.16 | Playbook execution, modules |
ansible-lint | >= 24.0 | Linting playbooks and roles |
jmespath | >= 1.0 | json_query filter support |
ansible-navigator | latest | Local execution and testing (optional, often provided by devcontainer) |
# Lint a playbook or role
ansible-lint playbooks/site.yml
# Syntax check without a live inventory
ansible-playbook --syntax-check playbooks/site.yml -i localhost,
# Install collections from requirements.yml
ansible-galaxy collection install -r requirements.yml
These aren't arbitrary rules — they reflect how the team actually works and what keeps production stable.
Always use fully qualified collection names. Never use short module names. The team uses
ansible-core with collections installed via requirements.yml, and FQCN prevents ambiguity
and makes collection dependencies explicit.
# Correct
- ansible.builtin.dnf:
name: chrony
state: present
# Wrong — never do this
- dnf:
name: chrony
state: present
Only create directories that contain files. An empty vars/ or meta/ directory is noise.
A role with only tasks needs only tasks/. Grow the structure as the role's needs grow.
A minimal role is just:
roles/my_role/
└── tasks/
└── main.yml
A role with configuration templates and tuneable defaults expands to:
roles/my_role/
├── defaults/
│ └── main.yml
├── handlers/
│ └── main.yml
├── tasks/
│ └── main.yml
└── templates/
└── my_config.j2
See references/role-reference.rst for the full pattern.
When a role does more than one logical thing, split tasks into focused files and delegate
from main.yml. This keeps diffs reviewable and lets team members reason about changes
in isolation.
# roles/vm_configure/tasks/main.yml
---
- name: Configure system packages
ansible.builtin.import_tasks: packages.yml
- name: Configure NTP via chrony
ansible.builtin.import_tasks: chrony.yml
- name: Configure SELinux policies
ansible.builtin.import_tasks: selinux.yml
Use import_tasks for static inclusion (the default). Use include_tasks only when you
need conditional or looped inclusion at runtime.
Playbooks are organised in numbered layers that run in dependency order, with a site.yml
that orchestrates the full stack and a preflight_checks.yml gate that validates prerequisites
before any changes.
playbooks/
├── site.yml # Orchestrator — imports layers in order
├── preflight_checks.yml # Validation gate — runs first
├── layer1_hypervisor.yml # Physical host / hypervisor setup
├── layer2_vm_config.yml # VM-level OS configuration
└── layer3_application.yml # Application-layer setup (example)
New playbooks should follow this layering convention. If a playbook doesn't fit a layer, it gets a descriptive name (not a number).
When a play modifies critical system state, prefer a two-phase approach:
--check --diff to preview changesFor handlers that restart services, include a verification step:
handlers:
- name: Restart chronyd
ansible.builtin.systemd:
name: chronyd
state: restarted
notify: Verify chronyd
- name: Verify chronyd
ansible.builtin.command:
cmd: chronyc tracking
changed_when: false
register: chrony_verify
failed_when: "'Leap status : Normal' not in chrony_verify.stdout"
Roles that modify system state should, where practical, capture the prior state and provide a tagged rollback path. This isn't always possible (you can't un-apply an SELinux policy trivially), but when it is — especially for configuration file changes — include it.
- name: Backup existing config
ansible.builtin.copy:
src: /etc/chrony.conf
dest: /etc/chrony.conf.ansible-backup
remote_src: true
tags: [chrony, rollback-prep]
The team uses static YAML inventories structured per environment:
inventories/
└── production/
├── hosts.yml
├── group_vars/
│ ├── all.yml
│ └── <group_name>.yml
└── host_vars/
└── <hostname>.yml
host_vars/all.yml for truly global settings (DNS, NTP servers, proxy config, etc.)thhs-p-rdl01)Use Ansible Vault for encrypting sensitive variables. Prefer encrypting individual
variables with ansible-vault encrypt_string over encrypting entire files — it keeps
diffs meaningful and lets team members see which variables exist even if they can't
read the values.
# group_vars/all.yml
proxy_password: !vault |
$ANSIBLE_VAULT;1.3;AES256
...encrypted content...
For secrets that need centralised lifecycle management or are shared across non-Ansible
systems, use the community.hashi_vault collection lookup plugin:
- name: Retrieve database password
ansible.builtin.set_fact:
db_password: "{{ lookup('community.hashi_vault.hashi_vault',
'secret/data/myapp:db_password',
url='https://vault.example.com') }}"
no_log: true
Always set no_log: true on tasks that handle secrets retrieved from Vault.
The team's local development workflow:
.devcontainer provides a consistent development environment across the teamansible-navigator for local execution and testing (stdout mode for quick runs,
interactive mode for debugging)When suggesting commands for local execution, prefer ansible-navigator run over
ansible-playbook directly:
# Local execution via navigator
ansible-navigator run playbooks/site.yml -i inventories/production/hosts.yml --mode stdout
# Syntax check
ansible-navigator run playbooks/site.yml --syntax-check --mode stdout
# Check mode (dry run)
ansible-navigator run playbooks/site.yml -i inventories/production/hosts.yml --check --diff --mode stdout
Key module choices for common infrastructure tasks:
ansible.builtin.dnf or ansible.builtin.apt depending on OS family;
use ansible_pkg_mgr fact or when: ansible_os_family == 'RedHat' guards to handle bothansible.builtin.systemd for systemd-managed services; ansible.builtin.service
as a portable fallback when the init system varies across targetsansible.posix.firewalld for firewalld-based distros; ansible.builtin.iptables
or community.general.ufw for othersansible.posix.seboolean, ansible.posix.selinux for policy;
community.general.sefcontext for file contexts — guard with when: ansible_selinux.status == 'enabled'/etc/chrony.conf or /etc/ntp.conf) rather
than modifying it in-place; use ansible.builtin.template + handler to restart the serviceansible.builtin.user, ansible.builtin.group; prefer uid/gid
pinning for consistency across hostsAny collection beyond ansible.builtin must be declared in requirements.yml at the project
root. FQCN usage makes these dependencies visible — if a task uses ansible.posix.firewalld,
the reader knows ansible.posix must be installed.
# requirements.yml
---
collections:
- name: ansible.posix
version: ">=1.5"
- name: community.general
version: ">=8.0"
- name: community.hashi_vault
version: ">=6.0"
Install collections into the project (not user-global):
ansible-galaxy collection install -r requirements.yml -p ./collections
Then configure ansible.cfg to pick up the local collections path:
[defaults]
collections_path = ./collections
This keeps the collection versions pinned per-project and avoids system-wide installs polluting other projects on the same machine.
"Install required packages", "Configure chrony NTP sources", "Enable and start firewalld"become: true at the play level when the entire play needs privilege, not per-taskGuides creation and editing of skills using test-driven development with pressure scenarios and subagents to verify agent compliance.
Forks private projects, strips secrets/internal references, verifies cleanliness, and packages them with CLAUDE.md, README, and setup.sh for safe public release.
npx claudepluginhub nq-rdl/agent-extensions --plugin ansible