| name | ansible-skill |
| description | Use when writing, reviewing, or debugging Ansible playbooks, roles, collections, inventory, Vault, Molecule, execution environments, or CI; diagnoses failure modes with explicit idempotency, blast-radius, secret exposure, variable precedence, inventory correctness, handler ordering, check-mode, supply-chain, and runtime risk framing. |
| license | Apache-2.0 |
| metadata | {"author":"sadicabubakari","version":"0.1.11"} |
Ansible Skill
Diagnose-first guidance for Ansible and ansible-core. Core file is a workflow; depth lives in reference files loaded on demand.
Response Contract
Every Ansible response must include:
- Assumptions & version floor —
ansible-core version, collections in requirements.yml with versions, Python interpreter target (ansible_python_interpreter), connection plugin (ssh/winrm/local), control node vs execution-environment runtime. State explicitly when the user did not provide them. When no version is given, assume the lowest supported ansible-core minor with EOL runway from the Version Matrix (currently 2.19+; 2.18 is security-only and near EOL). Recommending an EOL floor produces guidance that's already past its support window.
- Idempotency evidence — for each task introduced or modified: why
changed=True only when the world actually changed. Name the module's idempotency contract (native module idempotent; command/shell requires creates/removes/changed_when).
- Blast-radius controls — inventory target (hosts/groups/limit),
serial / max_fail_percentage / any_errors_fatal decision, --check + --diff coverage, whether this is safe to run against prod as-is.
- Risk category addressed — one or more of the 9 diagnose-table categories below.
- Chosen remediation & tradeoffs — what was chosen, what was traded off, why.
- Validation plan — exact commands tailored to runtime and risk tier:
ansible-lint, ansible-playbook --syntax-check, --check --diff, Molecule scenario, ansible-test sanity/units/integration.
- Rollback notes — for any destructive or state-mutating play: how to undo (inverse play, restore from backup,
state: absent), what evidence to keep (registered var output, command logs, diff artifacts).
Never recommend running a play against production without --check --diff first and an explicit --limit or a reviewed inventory pattern.
Workflow
- Capture execution context —
ansible-core version, collections, Python interpreter, connection plugin, execution path (local/CI/EE/AWX-free), environment criticality.
- Diagnose failure mode(s) using the routing table below. If intent spans categories, load all matching references.
- Load only the matching reference file(s) — do not preload depth the task does not need.
- Propose fix with risk controls — why this addresses the mode, what could still go wrong, guardrails (lint rules / Molecule /
--check --diff / approval gates / rollback).
- Generate artifacts — playbook YAML, role skeleton,
requirements.yml, Molecule scenario, CI workflow, Vault usage.
- Validate before finalizing — run validation commands tailored to risk tier.
- Emit the Response Contract at the end.
Diagnose Before You Generate
| Failure category | Symptoms | Primary references |
|---|
| Idempotency drift | Tasks report changed=True every run, command/shell without creates/changed_when, non-native modules, handlers firing spuriously | Idempotency Patterns |
| Blast radius | Missing serial, no max_fail_percentage, any_errors_fatal misused, no --limit in CI, fact-gathering against whole fleet | Execution & Runtime, CI/CD Workflows |
| Secret exposure | Plaintext in vars, no_log missing, Vault key handling, secrets in stdout/stderr, ansible-vault vs external secret managers | Security & Vault |
| Variable precedence bugs | 22-level chain surprises, set_fact vs vars vs vars_files, group_vars / host_vars collisions, extra-vars overrides | Inventory & Variables |
| Inventory correctness | Static/dynamic drift, group membership bugs, ansible_host vs inventory_hostname, missing --limit safety | Inventory & Variables |
| Handler/ordering issues | Handlers not firing on failure, meta: flush_handlers, listen topics, notify ordering | Idempotency Patterns |
| Check-mode blind spots | Tasks break under --check, ignore_errors / failed_when hiding real failures, modules that don't support check mode | Idempotency Patterns |
| Collection/role supply chain | Galaxy pinning, requirements.yml hygiene, version drift, private Automation Hub, signature verification | Collections & Supply Chain |
| Execution environment / runtime | EE image pinning, ansible-navigator vs ansible-playbook, Python interpreter discovery, connection plugin, become escalation, forks/pipelining/fact caching | Execution & Runtime |
When to Use This Skill
Activate when: creating or reviewing Ansible playbooks, roles, or collections; setting up or debugging Molecule / ansible-test; structuring multi-environment inventory; implementing Ansible CI/CD; choosing role patterns or collection organization; configuring Vault or external secret backends; building or pinning execution environments.
Don't use for: basic YAML syntax Claude already knows; module API reference (point users at the ansible-doc CLI or docs.ansible.com); AAP / AWX / Tower platform-specific questions (job templates, surveys, RBAC, workflows); cloud-provider SDK questions unrelated to Ansible modules.
Core Principles
Unit Hierarchy
| Unit | When to Use | Scope |
|---|
| Task | One action | Install a package, write a file |
| Role | Reusable bundle of related tasks | Web server config, database setup |
| Playbook | Orchestrates roles across hosts | Full stack deploy, one environment |
| Collection | Distributable unit of roles, modules, plugins | Shared across teams, versioned, on Galaxy or Automation Hub |
Flow: task → role → playbook → collection.
Directory Layout
inventories/
prod/ hosts, group_vars/, host_vars/
staging/ hosts, group_vars/, host_vars/
dev/
roles/ # local reusable roles
collections/ # requirements.yml, installed collections
playbooks/ # deploy.yml, site.yml, one-off ops plays
molecule/ # per-role scenarios
Separate inventories from roles. Keep roles single-responsibility. Keep all group_vars/ and host_vars/ inside each inventories/<env>/ directory — never at repo root. Repo-root group_vars//host_vars/ apply to every environment's plays and leak prod values into dev runs.
Naming Conventions
- Role names: short, snake_case, purpose-based (
nginx_site, not my_role). Hyphens fail the ansible-lint role-name rule (^[a-z][a-z0-9_]*$) and break collection packaging
- Variable names: prefix with role scope to avoid global collisions (
nginx_site_port, not port)
- Task names: imperative sentence — appears in logs (
"Install nginx", not "nginx")
- Tags: purpose-based, not task-name-based (
tags: [config, tls] vs tags: [install_nginx])
Task Ordering (within a task block)
name → module → module args → register → when → loop → notify → tags
- name: Install nginx
ansible.builtin.package:
name: nginx
state: present
register: nginx_install
when: ansible_os_family == 'Debian'
notify: restart nginx
tags: [install]
Idempotency — Quick Rule
| Situation | Use | Why |
|---|
| Native module available | ansible.builtin.<module> / fqcn module | Idempotent by contract |
| Must shell out, stateful output file | ansible.builtin.command with creates: / removes: | changed=True only if file missing |
| Must shell out, no clear file marker | ansible.builtin.command with changed_when: based on stdout/rc | Explicit change detection |
| Must shell out, needs shell features (pipes, redirects) | ansible.builtin.shell + changed_when | Last resort — harder to make safe |
Never: run ansible.builtin.shell without changed_when unless the task is genuinely informational and you also set changed_when: false.
See Idempotency Patterns for module idempotency contracts, handler patterns, and check-mode coverage.
Blast Radius — Quick Rule
| Target fleet size | Pattern | Play config |
|---|
| 1–5 hosts | Serial small batches | serial: 1 or serial: [1, 2] |
| 10–50 hosts, canary first | Canary + rolling | serial: [1, "25%"] with max_fail_percentage: 10 |
| 50+ hosts | Rolling with fail cap | serial: "10%", max_fail_percentage: 5 |
| Critical state-change on many hosts | Fail fast | any_errors_fatal: true |
| Independent tasks, no cascade | Free strategy | strategy: free (hosts run independently) |
Never run against production without an explicit --limit or a reviewed inventory pattern. Never set any_errors_fatal: true on rolling deploys where partial completion is safe — it turns a contained mid-batch failure into a global abort. Use any_errors_fatal: true precisely when partial completion is worse than full failure (schema migrations, bootstrap, replicated state); otherwise pair serial: with max_fail_percentage: and let the rollout continue.
See Execution & Runtime for serial/max-fail combinations and CI/CD Workflows for CI-level blast-radius gates.
Variable Precedence — Quick Rule
Abbreviated precedence (lowest → highest):
- Role defaults (
roles/<role>/defaults/main.yml)
- Inventory
group_vars/all
- Inventory
group_vars/<group>
- Inventory
host_vars/<host>
- Play vars
- Block vars
- Task vars
set_fact
--extra-vars (always wins)
Most common bugs:
set_fact values persist across plays in the same run — unexpected when debugging
group_vars/all silently overridden by host_vars/<host> — confusing when the same host appears in multiple groups
--extra-vars with @file.yml beats everything — a stray flag in CI can override protected config
See Inventory & Variables for the full 22-level ladder and collision examples.
Testing Strategy
Decision Matrix
| Situation | Approach | Tools | Cost |
|---|
| Syntax check | Static | ansible-playbook --syntax-check, ansible-lint | Free |
| Role unit test | Scenario-based | Molecule + docker/podman driver | Free–Low |
| Collection unit test | Module/plugin tests | ansible-test units | Free |
| Collection sanity | Import + schema | ansible-test sanity | Free |
| Integration — role | Molecule against real target | Molecule + delegated/vagrant driver | Med |
| Integration — collection | Live-run modules | ansible-test integration | Med |
| End-to-end, multi-host | Staged apply | --check --diff against staging | High |
Rules:
- Never skip
ansible-lint — it catches fqcn, no_log, changed_when misuse at PR time.
- Use
--check --diff as the last gate before any production play.
- Molecule for roles;
ansible-test for collections. They're not interchangeable.
See Testing Frameworks for Molecule scenario structure, ansible-test usage, and argument-specs for role input validation.
CI/CD
Pipeline stages: lint → syntax-check → Molecule (or ansible-test) → staged --check --diff → gated apply.
Rules:
- Pin
ansible-core to a currently supported minor in CI (e.g. ansible-core>=2.20,<2.21); cross-check the Version Matrix before each release cycle and bump off any minor that is past EOL or inside the final security-only window.
- Pin collections in
requirements.yml with exact versions for prod branches.
- Inject vault password via OIDC or masked secret — never a file in the repo.
- Treat the reviewed
--check --diff output as an approval artifact, not a replayable plan. The apply job must re-evaluate current state — optionally re-run --check --diff against live infrastructure and compare to the approved artifact before executing.
See CI/CD Workflows for GitHub Actions + GitLab CI templates and blast-radius approval gates.
Security & Vault
Don't:
- Store secrets in plaintext vars or unencrypted
vars_files
- Omit
no_log: true on tasks that pass secrets as module args
- Commit vault password files — use OIDC, system keyring, or external key providers
- Use
--verbose in CI on tasks handling secrets (output leaks to logs)
Do:
- Use
ansible-vault encrypt_string for inline single-value secrets
- Use Vault-id strategy to support per-environment keys
- Prefer external secret backends (HashiCorp Vault, AWS Secrets Manager, 1Password) via lookup plugins over static vault files
- Set
no_log: true on any task whose module args include secrets, and avoid leak paths that bypass it: secrets in task names, debug: output of registered values, related tasks that omit no_log, and any loop: whose item itself is a secret (mark the looping task no_log: true too)
See Security & Vault for vault-id patterns, external-backend lookups, and secrets-in-logs hardening.
Collections & Supply Chain
| Pin strategy | Prod | Dev |
|---|
requirements.yml collection version | Exact (version: "5.1.2") | Range (version: ">=5.1.0,<6.0.0") |
| Galaxy vs Automation Hub | Automation Hub for certified | Galaxy OK for experimental |
| Signature verification | Required | Optional for dev |
Rules:
-
Use fully-qualified collection names (ansible.builtin.copy, not copy) — ansible-lint fqcn rule enforces.
-
Pin all collections in requirements.yml; do not rely on the ansible community package version for prod.
-
Mirror critical collections internally (private Automation Hub or git) for supply-chain control.
-
Verify signatures on Automation Hub installs with the canonical flags (these are the ones that actually exist):
ansible-galaxy collection verify <coll> \
--server automation_hub \
--keyring /etc/pki/ansible/automation-hub-signing.gpg \
--collections-path ./collections \
--required-valid-signature-count +1
<coll> is required (the FQCN of an installed collection). To verify everything pinned in a manifest, swap the positional for -r requirements.yml. The --collections-path must match the --collections-path used at install time, otherwise verify searches Ansible's default paths and may report "not installed" or check a different copy.
The + prefix on --required-valid-signature-count is what makes verification strict: with +1 (or +all), the absence of any signature is itself an error, so an unsigned or misconfigured collection fails CI. Without the +, a bare 1 only checks "≥1 valid signature when signatures are present" and silently passes a zero-signature response. Pass --server automation_hub (matching a [galaxy_server.automation_hub] block in ansible.cfg) so the verify lookup hits the same registry the collection was installed from. In ansible.cfg, the GPG keyring config key is [galaxy] gpg_keyring. Flags like --signature-count-threshold or config keys like signing_keys do not exist.
-
Run both install commands separately into project-local paths so CI and the documented layout stay in sync — collection install -r ignores the roles: section, and role install falls back to Ansible's user roles path unless --roles-path is set:
ansible-galaxy collection install -r requirements.yml --collections-path ./collections
ansible-galaxy role install -r requirements.yml --roles-path ./roles
See Collections & Supply Chain for requirements.yml syntax, signature verification, and private-hub auth.
Execution Environments
| Situation | Use | Why |
|---|
| Local dev, fast iteration | Bare ansible-playbook + venv | No image build overhead |
| CI reproducibility | EE image with ansible-navigator | Pinned ansible-core + collections + deps |
| Production run | EE image, pulled by digest | Deterministic runs, rollback by switching to a previously approved digest |
Rules:
- Pin EE images by digest (
@sha256:...), not tag, for production.
- Build EEs with
ansible-builder from an execution-environment.yml.
ansible-navigator run is the preferred invocation — it handles EE lifecycle + streams output cleanly.
See Execution & Runtime for EE build patterns, interpreter discovery, connection/become gotchas, and forks/pipelining/fact-caching.
Version Management
| Component | Strategy | Example |
|---|
ansible-core runtime | Pin minor for prod; pick a non-EOL minor from the Version Matrix | ansible-core>=2.20,<2.21 (re-evaluate at each release cycle) |
Community ansible package | Pin exact or avoid in prod | Prefer pinning ansible-core + collections separately |
| Collections (prod) | Exact version in requirements.yml | version: "5.1.2" |
| Collections (dev) | Allow minor | version: ">=5.1.0,<6.0.0" |
| Python interpreter | Explicit ansible_python_interpreter | /usr/bin/python3 (avoid auto-discovery in prod) |
Keep ansible-core + collection upgrades in a separate PR from functional changes. The community ansible package is a starter bundle; production teams pin collections individually.
Modern Ansible Features (2.4+)
| Feature | Min ansible-core | Common use |
|---|
validate: parameter on template/copy | 2.0+ | Run shell validator before applying |
import_role / include_role with vars_from | 2.4+ | Load alt var files per import |
argument_specs in meta/ | 2.11+ | Role input validation |
Role handler listen topic inheritance | 2.16+ | Cross-role handler topics |
Structured changed_when with dict returns | 2.17+ | Clean branching on module result |
ansible-navigator stable workflow | n/a (packaging) | Default for EE-based runs |
Verify the runtime floor before emitting a feature. Version-specific behavior (esp. validate and handler inheritance) is a frequent LLM mistake.
Reference Files
Progressive disclosure — essentials here, depth on demand.
- Idempotency Patterns — module contracts,
command/shell guards, handlers, check-mode
- Inventory & Variables — full 22-level precedence, dynamic inventory,
set_fact vs vars
- Security & Vault — vault-id, external secret backends,
no_log, log hardening
- Execution & Runtime — serial/max-fail, execution environments, interpreter, connection/become, performance
- Testing Frameworks — ansible-lint, Molecule, ansible-test, argument_specs
- CI/CD Workflows — GitHub Actions, GitLab CI, blast-radius gates, secret handling in CI
- Collections & Supply Chain —
requirements.yml, fqcn, signature verification, private hubs
- Quick Reference — command cheatsheet, troubleshooting flowchart, module recipes, version matrix