Write, review, and architect Ansible automation - from single playbooks to multi-tier, compliance-hardened infrastructure management. The goal is idempotent, auditable, maintainable automation that works the same locally and in CI/CD.
AWX 24.6.1 (last formal release Jul 2024; upstream AWX releases paused for a major refactor, devel branch active - track ansible/awx; awx-operator ~2.12.x still ships for K8s deploys). Verify current AWX/AAP release status before recommending a specific version or install path.
Building Execution Environments for consistent runtime
Integrating Ansible into CI/CD pipelines (GitLab CI, GitHub Actions)
Reviewing AI-generated playbooks for correctness and idiomatic patterns
When NOT to use
Infrastructure provisioning (VPCs, RDS, EC2, cloud resources) - use terraform
Kubernetes manifests, Helm charts, cluster architecture - use kubernetes
Dockerfiles, Compose stacks, container image optimization - use docker
CI/CD pipeline design (stages, runners, caching) - use ci-cd
Security audits of application code (SAST, dependency scanning) - use security-audit
Shell scripting or one-off commands - use command-prompt
Firewall appliance management (OPNsense/pfSense) - use firewall-appliance
Single-machine OS-level admin questions (package setup, user management, service config without automation context) - use the appropriate distro skill: debian-ubuntu, rhel-fedora, kali-linux, or arch-btw
AI Self-Check
AI tools consistently produce the same Ansible mistakes. Before returning any generated playbook, role, or task, verify against this list:
FQCNs used everywhere (ansible.builtin.copy, not copy). AI almost never does this unprompted.
become: true present where privilege escalation is needed (AI often forgets this)
no_log: true on every task handling secrets, passwords, tokens, or API keys (CVE-2024-8775 proved this matters)
Every task has a descriptive name: field (AI sometimes omits names on simple tasks)
Handler names are unique and notify: strings match exactly (typos = silent failures)
Variables use {{ var }} with quotes: "{{ my_var }}" not {{ my_var }} (bare Jinja2 without quotes breaks YAML parsing)
No command/shell/raw when an Ansible module exists for the operation
Tasks are idempotent - running twice produces the same result (watch command/shell tasks without creates/removes)
No hardcoded values - IPs, paths, package versions, usernames go in variables with defaults
ansible.builtin.apt/ansible.builtin.dnf use state: present, not state: latest (unless explicitly upgrading)
Loop variable is item (default) or renamed via loop_var in nested loops (AI conflates loop variables)
block/rescue/always used for error handling, not bare ignore_errors: true
No ansible.builtin.template with src: pointing to a non-.j2 file (confusing, even if it works)
changed_when/failed_when set on command/shell tasks to prevent false change reports
Tags present on logical task groups for selective execution
Run generated playbooks through ansible-lint (production profile) when available.
Current source checked: dated versions, CLI flags, API names, and support windows are verified against primary docs before repeating them
Hidden state identified: local config, credentials, caches, contexts, branches, cluster targets, or previous runs are made explicit before acting
Verification is real: final checks exercise the actual runtime, parser, service, or integration point instead of only linting prose or happy paths
Routing overlap checked: overlapping skills, trigger terms, and "When NOT to use" boundaries are checked before returning guidance
Spec claims verified: claims about tool behavior, output contracts, or repo conventions are checked against current docs, scripts, or skill files
Collection docs checked: module arguments and return values match the installed collection version
Idempotence proven: changed/ok behavior is verified with check mode or a second run where practical
Performance
Use targeted inventories, tags, and --limit for large fleets; avoid full-fleet runs while iterating on a single role.
Gather only required facts and cache facts where supported for slow or high-latency environments.
Prefer native modules over shell loops so Ansible can batch work, diff safely, and report idempotence.
Best Practices
Pin collection versions in requirements.yml for production automation.
Run destructive playbooks with --check --diff first and require a human-reviewed limit for production hosts.
Keep Vault values out of diffs, logs, callback output, and generated examples.
Workflow
Step 1: Determine the domain
Based on the request:
"Write a playbook to configure X" -> Playbooks
"Create a reusable role for X" -> Roles & Collections
Variable precedence (22 levels - the most common source of confusion). In ascending priority:
Role defaults (defaults/main.yml) - weakest, meant to be overridden
Inventory vars (group_vars/, host_vars/)
Play vars
Task vars
Extra vars (-e) - strongest, overrides everything
Rule of thumb: put defaults in role defaults/, environment-specific values in group_vars/, one-off overrides in host_vars/, and emergency overrides via -e.
Handlers: only run when notified by a changed task, execute once at the end of the play (not immediately). Key gotchas:
Handler names must be unique across all included roles
Handlers don't run if the play fails before reaching them (use meta: flush_handlers if needed)
Handlers run in definition order, not notification order
Multiple notifications to the same handler = one execution
Blocks: use block/rescue/always for error handling and rollback - see playbook-patterns.md for complete deploy-with-rollback examples. Prefer block/rescue over ignore_errors: true.
Loops: prefer loop: over deprecated with_* syntax. Use loop_control.label for clean output.
Conditional execution: when: ansible_os_family == "Debian" etc. For multi-OS roles, use conditionals or include_tasks per OS family. See playbook-patterns.md for Alpine/OpenRC patterns.
Service management: use ansible.builtin.service (generic) for cross-distro roles - it auto-detects systemd, OpenRC, SysV via ansible_service_mgr. Only use ansible.builtin.systemd when you need systemd-specific features (daemon_reload, scope). See playbook-patterns.md for OpenRC patterns.
Shell profile changes: when converting a manual shell profile tweak into Ansible,
prefer a dedicated reusable role with ansible.builtin.blockinfile, role-prefixed
defaults, and a dedicated rollout playbook. See references/operations-and-execution.md;
the block must guard on SSH, not already inside
tmux, real TTY on stdin/stdout, and usable TERM, so automation, scp, rsync,
and remote SSH commands are not hijacked.
Registering results: register: result_var stores task output. Use when: result_var.stat.exists, result_var.rc == 0, etc. See playbook-patterns.md for patterns.
Vault Quick Reference
# Encrypt a single variable (inline in YAML)
ansible-vault encrypt_string 'supersecret' --name 'db_password'
# Encrypt an entire file
ansible-vault encrypt group_vars/production/secrets.yml
# Edit encrypted file
ansible-vault edit group_vars/production/secrets.yml
# Run playbook with vault
ansible-playbook site.yml --ask-vault-pass
# Or with a password file (for CI/CD)
ansible-playbook site.yml --vault-password-file ~/.vault_pass
Never store the vault password in plaintext alongside the repo. Use --ask-vault-pass, a password file outside the repo, or a vault script that fetches from a secret manager.
FIM agent deployed and configured (AIDE/OSSEC) (Req 11.5)
All secrets Vault-encrypted, no_log: true everywhere (Req 8.6.2)
Password policies enforced via PAM (Req 8.3.6)
Playbook execution logged and archived (Req 10, Req 6)
Anti-malware deployed on all in-scope systems (Req 5.2)
NTP configured for consistent timestamps (Req 10.6)
Unnecessary services disabled (Req 2.2.4)
Deprecations and Breaking Changes
ansible-core 2.20 (current)
Removals (already removed):
smart transport value - choose ssh or paramiko explicitly
Galaxy v2 API support - Galaxy servers must support v3
PARAMIKO_HOST_KEY_AUTO_ADD and PARAMIKO_LOOK_FOR_KEYS config keys
passlib_or_crypt API from encrypt utility
Deprecations (removal in 2.24):
INJECT_FACTS_AS_VARS defaults to True but will flip to False. Access facts via ansible_facts['hostname'] instead of ansible_hostname. Start migrating now.
ansible.module_utils._text imports (to_bytes, to_native, to_text) - use ansible.module_utils.common.text.converters instead
vars internal variable cache
ansible-core 2.19 (previous)
Data Tagging overhaul: improved error reporting but some loop templates broke (GitHub issue #85605). If loops fail with type errors after upgrading, check for native Jinja2 type handling conflicts.
CalVer migration
All Ansible DevTools projects (molecule, ansible-lint, ansible-navigator, tox-ansible) switched from SemVer to CalVer (YY.MM.MICRO) in 2024. Don't be confused by the version jump (e.g., ansible-lint 6.x -> 26.x).
Upgrade to ansible-core >= 2.16.14, 2.17.7, or 2.18.1
CVE-2024-8775
Medium
Vault-encrypted variables exposed in plaintext via include_vars without no_log
Add no_log: true to all secret-handling tasks
CVE-2025-14010
Medium
community.general exposes Keycloak credentials in verbose output
Upgrade to community.general >= 12.2.0
CVE-2025-49520
High
EDA authenticated argument injection in Git URL (command execution)
Patch AAP/EDA
CVE-2025-49521
High
EDA template injection via Git branch/refspec (command execution)
Patch AAP/EDA
Supply chain
Galaxy has no package signing or hash verification. Academic research (2025) found 45 vulnerable dependency chains across 482 Galaxy repos, with 38-54% code overlap propagating vulnerabilities.
Pin collection versions in requirements.yml. Prefer Automation Hub (Red Hat certified) over Galaxy for production-critical collections.
Pin GitHub Actions to commit SHAs in CI/CD (not mutable tags).
Scan EE images for CVEs like any container image.
AI-generated playbook risks
AI tools hallucinate module names and parameters. Verify every module exists in the target collection version.
AI rarely adds no_log: true to secret-handling tasks.
AI generates non-idempotent command/shell tasks where modules exist.
AI uses bare module names instead of FQCNs.
Slopsquatting: AI may suggest Galaxy roles or collections that don't exist. Verify on Galaxy before adding to requirements.yml.
Reference Files
references/playbook-patterns.md - playbook and task patterns for common automation work
references/roles-and-collections.md - role anatomy, collection structure, Galaxy patterns, and Molecule workflows
references/vault-and-secrets.md - Vault usage, secret handling, and external secret-manager integration
references/compliance.md - PCI-DSS and CIS-oriented hardening guidance
Output Contract
See skills/_shared/output-contract.md for the full contract.
Skill name: ANSIBLE
Deliverable bucket:audits
Mode: conditional. When invoked to analyze, review, audit, or improve existing repo content, emit the full contract - boxed inline header, body summary inline plus per-finding detail in the deliverable file, boxed conclusion, conclusion table - and write the deliverable to docs/local/audits/ansible/<YYYY-MM-DD>-<slug>.md. When invoked to answer a question, teach a concept, build a new artifact, or generate content, respond freely without the contract.
Severity scale:P0 | P1 | P2 | P3 | info (see shared contract; only used in audit/review mode).
kubernetes - for K8s manifests, Helm charts, cluster architecture. Ansible can deploy to
K8s via kubernetes.core collection, but manifest design belongs in the kubernetes skill.
docker - for Dockerfile and Compose patterns. Ansible can manage containers via
community.docker, but image building and Compose design belong in the docker skill.
databases - for engine configuration (postgresql.conf, pg_hba.conf). Ansible automates
the deployment of those configs; databases skill owns the tuning decisions.
ci-cd - for pipeline design. Ansible can be called from CI/CD pipelines, but pipeline
structure (stages, jobs, caching) belongs in the ci-cd skill.
security-audit - for auditing Ansible playbooks for credential exposure, vault misuse,
or supply chain risks in Galaxy dependencies.
debian-ubuntu - for Debian/Ubuntu/Mint OS-level admin questions outside an automation context.
rhel-fedora - for RHEL/Fedora/CentOS OS-level admin questions outside an automation context.
kali-linux - for Kali Linux administration outside an automation context.
arch-btw - for Arch Linux / CachyOS OS-level admin questions outside an automation context.
Rules
These are non-negotiable. Violating any of these is a bug.
FQCNs everywhere.ansible.builtin.copy, not copy. No exceptions.
Idempotent by default. Every task must be safe to run multiple times. command/shell tasks need creates/removes or changed_when.
no_log: true on secrets. Every task handling passwords, tokens, API keys, or sensitive data. CVE-2024-8775 proved the cost of forgetting this.
No command/shell when a module exists. Modules are idempotent, tested, and portable. Shell commands are none of those.
Variables over hardcoded values. IPs, paths, package versions, usernames, ports - all variables with defaults.
Quote Jinja2 variables."{{ var }}", not {{ var }}. Bare braces break YAML parsing.
Vault for secrets. Not plaintext in group_vars, not ansible_ssh_pass in inventory, not environment variables in playbooks.
Test with Molecule. Every role gets a Molecule scenario with converge + idempotence check + verification.
Pin collection versions. In requirements.yml and EE definitions. Unpinned collections are a supply chain risk.
ansible-lint clean. Production profile. In CI. On every change.
Separate inventory per environment. Production, staging, dev. Never a single inventory with --limit for environment selection.
--check --diff before apply. Review what will change before applying, especially in CI/CD.
Run the AI self-check. Every generated playbook gets verified against the checklist above before returning.
1---2name: ansible-23description: · Write/review Ansible playbooks, roles, inventories, Vault, Molecule, AWX/AAP. Triggers: 'ansible', 'playbook', 'role', 'inventory', 'group_vars', 'ansible-lint'.4license: MIT5---67# Ansible: Production Configuration Management
89Write, review, and architect Ansible automation - from single playbooks to multi-tier, compliance-hardened infrastructure management. The goal is idempotent, auditable, maintainable automation that works the same locally and in CI/CD.
1011**Target versions** (May 2026):
12- ansible-core **2.20.x LTS** (Python 3.12+ controller, 3.9+ target, EOL May 2027)
13- ansible (community package) 13.x (depends on ansible-core 2.20)
14- molecule 26.x (CalVer), ansible-lint 26.x (CalVer), ansible-navigator 26.x (CalVer)
15- ansible-builder 3.1.x (EE definition v3)
16- AWX 24.6.1 (last formal release Jul 2024; upstream AWX releases paused for a major refactor, devel branch active - track ansible/awx; awx-operator ~2.12.x still ships for K8s deploys). Verify current AWX/AAP release status before recommending a specific version or install path.
17- AAP 2.6 (Oct 2025 - last RPM-installable release; AAP 2.7+ containerized-only)
1819This skill covers four domains depending on context:
20- **Playbooks** - tasks, handlers, variables, conditions, loops, blocks, templates, Jinja2
21- **Roles & Collections** - role structure, collection packaging, Galaxy/Automation Hub, Molecule testing
22- **Operations** - inventory, Execution Environments, CI/CD integration, Vault, ansible-navigator
23- **Compliance** - PCI-DSS 4.0 hardening, CIS benchmarks, Ansible-Lockdown, audit logging
2425## When to use
2627- Writing or reviewing Ansible playbooks, roles, or collections
28- Configuring servers after Terraform provisions them (day-2 operations)
29- OS hardening (CIS benchmarks, STIG, PCI-DSS configuration requirements)
30- Managing packages, services, users, firewall rules, cron jobs, config files
31- Testing automation with Molecule or tox-ansible
32- Setting up Ansible Vault for secrets management
33- Designing inventory structures (static, dynamic, multi-environment)
34- Building Execution Environments for consistent runtime
35- Integrating Ansible into CI/CD pipelines (GitLab CI, GitHub Actions)
36- Reviewing AI-generated playbooks for correctness and idiomatic patterns
3738## When NOT to use
3940- Infrastructure provisioning (VPCs, RDS, EC2, cloud resources) - use **terraform**
41- Kubernetes manifests, Helm charts, cluster architecture - use **kubernetes**
42- Dockerfiles, Compose stacks, container image optimization - use **docker**
43- CI/CD pipeline design (stages, runners, caching) - use **ci-cd**
44- Security audits of application code (SAST, dependency scanning) - use **security-audit**
45- Shell scripting or one-off commands - use **command-prompt**
46- Firewall appliance management (OPNsense/pfSense) - use **firewall-appliance**
47- Single-machine OS-level admin questions (package setup, user management, service config without automation context) - use the appropriate distro skill: **debian-ubuntu**, **rhel-fedora**, **kali-linux**, or **arch-btw**
4849---
5051## AI Self-Check
5253AI tools consistently produce the same Ansible mistakes. **Before returning any generated playbook, role, or task, verify against this list:**
5455- [ ] FQCNs used everywhere (`ansible.builtin.copy`, not `copy`). AI almost never does this unprompted.
56- [ ] `become: true` present where privilege escalation is needed (AI often forgets this)
57- [ ] `no_log: true` on every task handling secrets, passwords, tokens, or API keys (CVE-2024-8775 proved this matters)
58- [ ] Every task has a descriptive `name:` field (AI sometimes omits names on simple tasks)
59- [ ] Handler names are unique and `notify:` strings match exactly (typos = silent failures)
60- [ ] Variables use `{{ var }}` with quotes: `"{{ my_var }}"` not `{{ my_var }}` (bare Jinja2 without quotes breaks YAML parsing)
61- [ ] No `command`/`shell`/`raw` when an Ansible module exists for the operation
62- [ ] Tasks are idempotent - running twice produces the same result (watch `command`/`shell` tasks without `creates`/`removes`)
63- [ ] No hardcoded values - IPs, paths, package versions, usernames go in variables with defaults
64- [ ] `ansible.builtin.apt`/`ansible.builtin.dnf` use `state: present`, not `state: latest` (unless explicitly upgrading)
65- [ ] Loop variable is `item` (default) or renamed via `loop_var` in nested loops (AI conflates loop variables)
66- [ ] `block`/`rescue`/`always` used for error handling, not bare `ignore_errors: true`
67- [ ] No `ansible.builtin.template` with `src:` pointing to a non-`.j2` file (confusing, even if it works)
68- [ ] `changed_when`/`failed_when` set on `command`/`shell` tasks to prevent false change reports
69- [ ] Tags present on logical task groups for selective execution
7071Run generated playbooks through `ansible-lint` (production profile) when available.
72- [ ] **Current source checked**: dated versions, CLI flags, API names, and support windows are verified against primary docs before repeating them
73- [ ] **Hidden state identified**: local config, credentials, caches, contexts, branches, cluster targets, or previous runs are made explicit before acting
74- [ ] **Verification is real**: final checks exercise the actual runtime, parser, service, or integration point instead of only linting prose or happy paths
75- [ ] **Routing overlap checked**: overlapping skills, trigger terms, and "When NOT to use" boundaries are checked before returning guidance
76- [ ] **Spec claims verified**: claims about tool behavior, output contracts, or repo conventions are checked against current docs, scripts, or skill files
77- [ ] **Collection docs checked**: module arguments and return values match the installed collection version
78- [ ] **Idempotence proven**: changed/ok behavior is verified with check mode or a second run where practical
7980---
8182## Performance
8384- Use targeted inventories, tags, and `--limit` for large fleets; avoid full-fleet runs while iterating on a single role.
85- Gather only required facts and cache facts where supported for slow or high-latency environments.
86- Prefer native modules over shell loops so Ansible can batch work, diff safely, and report idempotence.
878889---
9091## Best Practices
9293- Pin collection versions in `requirements.yml` for production automation.
94- Run destructive playbooks with `--check --diff` first and require a human-reviewed limit for production hosts.
95- Keep Vault values out of diffs, logs, callback output, and generated examples.
969798## Workflow
99100### Step 1: Determine the domain
101102Based on the request:
103- **"Write a playbook to configure X"** -> Playbooks
104- **"Create a reusable role for X"** -> Roles & Collections
105- **"Set up inventory" / "CI/CD" / "vault" / "EE"** -> Operations
106- **"Harden this server" / "CIS benchmark" / "PCI compliance"** -> Compliance
107- **"Review this playbook/role"** -> Apply production checklist + critical rules + AI self-check
108109Most real tasks blend domains. Start with the playbook, extract to roles when reuse is clear, wire into operations last.
110111### Step 2: Gather requirements
112113Before writing YAML, determine:
114- **Target OS**: RHEL/CentOS, Ubuntu/Debian, Alpine, Windows - affects module choices
115- **Python version on targets**: ansible-core 2.20 requires Python 3.9+ on managed nodes
116- **Privilege escalation**: `become` method (sudo, su, doas, runas for Windows)
117- **Connection**: SSH (default), WinRM (Windows), local, network_cli (network devices)
118- **Idempotency**: every task must be safe to run multiple times
119- **Secrets**: Ansible Vault, HashiCorp Vault, CI/CD secrets, environment variables
120- **Testing**: Molecule scenario? tox-ansible matrix? Integration tests?
121- **Compliance**: PCI-DSS scope? CIS benchmark level? STIG profile?
122- **Inventory**: static, dynamic (cloud), or hybrid? Multi-environment?
123- **Execution**: ansible-playbook (direct), ansible-navigator (EE), AWX/AAP (platform)?
124125### Step 3: Build
126127Follow the domain-specific section below. Always apply the production checklist (Step 4) and AI self-check before finishing.
128129### Step 4: Validate
130131```bash
132# Syntax check (fast, no connection needed)
133ansible-playbook playbook.yml --syntax-check
134135# Lint (use production profile for strictest checks)
136ansible-lint --profile production playbook.yml
137138# Dry run (needs inventory + connectivity)
139ansible-playbook playbook.yml --check --diff
140141# Molecule (role testing)
142molecule test # full cycle: create, converge, verify, destroy
143molecule converge # just apply (dev loop)
144molecule verify # run verification only
145146# Navigator (EE-based execution)
147ansible-navigator run playbook.yml --mode stdout --eei <ee-image>
148```
149150---
151152## Playbooks
153154Read `references/playbook-patterns.md` for complete, copy-pasteable task examples (services, packages, files, templates, users, firewall, cron, systemd, OpenRC) and Jinja2 patterns.
155156### Structure
157158```yaml
159---
160- name: Configure web servers
161 hosts: webservers
162 become: true
163 gather_facts: true
164165 vars:
166 app_port: 8080
167 app_user: appuser
168169 pre_tasks:
170 - name: Update apt cache
171 ansible.builtin.apt:
172 update_cache: true
173 cache_valid_time: 3600
174 when: ansible_os_family == "Debian"
175176 roles:
177 - role: common
178 tags: [common]
179 - role: nginx
180 tags: [nginx]
181182 tasks:
183 - name: Ensure application directory exists
184 ansible.builtin.file:
185 path: /opt/app
186 state: directory
187 owner: "{{ app_user }}"
188 mode: "0755"
189190 handlers:
191 - name: Restart nginx
192 ansible.builtin.systemd:
193 name: nginx
194 state: restarted
195 daemon_reload: true
196```
197198### Key patterns
199200**Variable precedence** (22 levels - the most common source of confusion). In ascending priority:
2011. Role defaults (`defaults/main.yml`) - weakest, meant to be overridden
2022. Inventory vars (`group_vars/`, `host_vars/`)
2033. Play vars
2044. Task vars
2055. Extra vars (`-e`) - strongest, overrides everything
206207**Rule of thumb**: put defaults in role `defaults/`, environment-specific values in `group_vars/`, one-off overrides in `host_vars/`, and emergency overrides via `-e`.
208209**Handlers**: only run when notified by a changed task, execute once at the end of the play (not immediately). Key gotchas:
210- Handler names must be unique across all included roles
211- Handlers don't run if the play fails before reaching them (use `meta: flush_handlers` if needed)
212- Handlers run in definition order, not notification order
213- Multiple notifications to the same handler = one execution
214215**Blocks**: use `block`/`rescue`/`always` for error handling and rollback - see `playbook-patterns.md` for complete deploy-with-rollback examples. Prefer `block`/`rescue` over `ignore_errors: true`.
216217**Loops**: prefer `loop:` over deprecated `with_*` syntax. Use `loop_control.label` for clean output.
218219**Conditional execution**: `when: ansible_os_family == "Debian"` etc. For multi-OS roles, use conditionals or `include_tasks` per OS family. See `playbook-patterns.md` for Alpine/OpenRC patterns.
220221**Service management**: use `ansible.builtin.service` (generic) for cross-distro roles - it auto-detects systemd, OpenRC, SysV via `ansible_service_mgr`. Only use `ansible.builtin.systemd` when you need systemd-specific features (`daemon_reload`, `scope`). See `playbook-patterns.md` for OpenRC patterns.
222223**Shell profile changes**: when converting a manual shell profile tweak into Ansible,
224prefer a dedicated reusable role with `ansible.builtin.blockinfile`, role-prefixed
225defaults, and a dedicated rollout playbook. See `references/operations-and-execution.md`;
226the block must guard on SSH, not already inside
227tmux, real TTY on stdin/stdout, and usable `TERM`, so automation, `scp`, `rsync`,
228and remote SSH commands are not hijacked.
229230**Registering results**: `register: result_var` stores task output. Use `when: result_var.stat.exists`, `result_var.rc == 0`, etc. See `playbook-patterns.md` for patterns.
231232### Vault Quick Reference
233234```bash
235# Encrypt a single variable (inline in YAML)
236ansible-vault encrypt_string 'supersecret' --name 'db_password'
237238# Encrypt an entire file
239ansible-vault encrypt group_vars/production/secrets.yml
240241# Edit encrypted file
242ansible-vault edit group_vars/production/secrets.yml
243244# Run playbook with vault
245ansible-playbook site.yml --ask-vault-pass
246# Or with a password file (for CI/CD)
247ansible-playbook site.yml --vault-password-file ~/.vault_pass
248```
249250Never store the vault password in plaintext alongside the repo. Use `--ask-vault-pass`, a password file outside the repo, or a vault script that fetches from a secret manager.
251252### What NOT to write
253254- `command: apt-get install -y nginx` (use `ansible.builtin.apt`)
255- `shell: systemctl restart nginx` (use `ansible.builtin.systemd`)
256- `shell: useradd deploy` (use `ansible.builtin.user`)
257- `copy` without `mode:` on sensitive files (defaults to umask, unpredictable)
258- `template` without `.j2` extension on the source file
259- `ignore_errors: true` without a comment explaining why (use `block`/`rescue` instead)
260- `with_items` (deprecated - use `loop:`)
261- Bare `{{ var }}` without quotes (YAML parses it as a dict start)
262- `gather_facts: true` + never using facts (wasted 5-15 seconds per host)
263- Tasks without `name:` (legal but unreadable in output)
264- `state: latest` in production playbooks (non-deterministic - pin versions)
265266---
267268## Roles & Collections
269270Read `references/roles-and-collections.md` for detailed role anatomy, collection structure, Galaxy patterns, and Molecule testing workflows.
271272- Use one responsibility per role.
273- Put user-tunable values in `defaults/main.yml`, not `vars/main.yml`.
274- Use FQCNs everywhere.
275- Prefix role variables to avoid collisions.
276- Treat Molecule idempotence checks as mandatory, not optional polish.
277278---
279280## Operations
281- Read `references/operations-and-execution.md` for inventory layout, `ansible.cfg`, execution environments, CI/CD integration, and `ansible-navigator`.
282- Keep inventory split by environment.
283- Prefer YAML inventory over legacy INI when touching existing inventories.
284- Treat `pipelining = True`, fact caching, and callback configuration as standard production defaults.
285- Use execution environments for repeatable local and CI runs.
286- Keep vault usage in `references/vault-and-secrets.md`; secrets stay encrypted, prefixed, and wrapped with `no_log: true`.
287288---
289290## Compliance
291292Read `references/compliance.md` for the full PCI-DSS 4.0 requirements mapping to Ansible controls, CIS benchmark automation, and hardening patterns.
293294- Ansible owns OS and service enforcement, not application-level security review.
295- CIS and PCI controls should be treated as role and template inputs, not blindly applied defaults.
296- Test benchmark hardening in staging before broad rollout.
297- Preserve audit evidence with callback plugins, AWX/AAP activity streams, or CI artifacts.
298299---
300301## Production Checklist
302303### Playbooks
304305- [ ] FQCNs on every module (`ansible.builtin.*`, `community.general.*`, etc.)
306- [ ] Every task has a descriptive `name:`
307- [ ] `become: true` only where needed (not play-level unless every task requires it)
308- [ ] `no_log: true` on all tasks handling secrets
309- [ ] Variables quoted: `"{{ var }}"` not `{{ var }}`
310- [ ] No `command`/`shell` when a module exists
311- [ ] `changed_when`/`failed_when` on all `command`/`shell` tasks
312- [ ] Handlers have unique names and `notify:` strings match exactly
313- [ ] Tags on logical task groups
314- [ ] `--check` mode works (no tasks that break in check mode without `check_mode: false`)
315- [ ] Idempotent - running twice produces no changes on the second run
316- [ ] No `state: latest` in production (pin package versions)
317- [ ] `ansible-lint --profile production` passes clean
318319### Roles
320321- [ ] All variables prefixed with role name (`nginx_port`, not `port`)
322- [ ] `defaults/main.yml` for all user-configurable values
323- [ ] `meta/main.yml` with dependencies, platforms, and minimum ansible version
324- [ ] Molecule test scenario with converge + idempotence + verify
325- [ ] README with usage examples and variable documentation
326- [ ] No hardcoded values in `tasks/` (everything parameterized)
327- [ ] `handlers/main.yml` for service restarts (not inline restarts in tasks)
328329### Operations
330331- [ ] Inventory separated by environment (production, staging, dev)
332- [ ] `group_vars/` and `host_vars/` for environment-specific config
333- [ ] Vault-encrypted secrets in dedicated `vault.yml` files
334- [ ] Vault password via `--vault-password-file` (not interactive prompt in CI)
335- [ ] SSH key-based auth (no `ansible_ssh_pass` in inventory)
336- [ ] EE image pinned to specific tag (not `:latest`)
337- [ ] ansible.cfg committed with sane defaults (no `host_key_checking = False` in production)
338- [ ] Collections pinned in `requirements.yml` with version constraints
339- [ ] `ansible-lint` in CI pipeline (production profile)
340341### Compliance (PCI-DSS 4.0)
342343- [ ] CIS benchmark role applied and tested (Req 2.2)
344- [ ] SSH hardened: key-only auth, no root login, protocol 2, idle timeout (Req 2.2.7)
345- [ ] Firewall rules managed as code (Req 1)
346- [ ] Auditd rules deployed for CDE systems (Req 10.2)
347- [ ] Log forwarding to immutable SIEM (Req 10.4.1.1)
348- [ ] FIM agent deployed and configured (AIDE/OSSEC) (Req 11.5)
349- [ ] All secrets Vault-encrypted, `no_log: true` everywhere (Req 8.6.2)
350- [ ] Password policies enforced via PAM (Req 8.3.6)
351- [ ] Playbook execution logged and archived (Req 10, Req 6)
352- [ ] Anti-malware deployed on all in-scope systems (Req 5.2)
353- [ ] NTP configured for consistent timestamps (Req 10.6)
354- [ ] Unnecessary services disabled (Req 2.2.4)
355356---
357358## Deprecations and Breaking Changes
359360### ansible-core 2.20 (current)
361362**Removals (already removed)**:
363- `smart` transport value - choose `ssh` or `paramiko` explicitly
364- Galaxy v2 API support - Galaxy servers must support v3
365- `PARAMIKO_HOST_KEY_AUTO_ADD` and `PARAMIKO_LOOK_FOR_KEYS` config keys
366- `passlib_or_crypt` API from encrypt utility
367368**Deprecations (removal in 2.24)**:
369- `INJECT_FACTS_AS_VARS` defaults to True but will flip to False. Access facts via `ansible_facts['hostname']` instead of `ansible_hostname`. Start migrating now.
370- `ansible.module_utils._text` imports (`to_bytes`, `to_native`, `to_text`) - use `ansible.module_utils.common.text.converters` instead
371- `vars` internal variable cache
372373### ansible-core 2.19 (previous)
374375- **Data Tagging** overhaul: improved error reporting but some loop templates broke (GitHub issue #85605). If loops fail with type errors after upgrading, check for native Jinja2 type handling conflicts.
376377### CalVer migration
378379All Ansible DevTools projects (molecule, ansible-lint, ansible-navigator, tox-ansible) switched from SemVer to CalVer (`YY.MM.MICRO`) in 2024. Don't be confused by the version jump (e.g., ansible-lint 6.x -> 26.x).
380381---
382383## Security Considerations
384385### CVEs to know
386387| CVE | Severity | Description | Mitigation |
388|-----|----------|-------------|------------|
389| CVE-2024-11079 | Medium | Hostvars bypass unsafe content protections, enabling arbitrary code execution via templated content | Upgrade to ansible-core >= 2.16.14, 2.17.7, or 2.18.1 |
390| CVE-2024-8775 | Medium | Vault-encrypted variables exposed in plaintext via `include_vars` without `no_log` | Add `no_log: true` to all secret-handling tasks |
391| CVE-2025-14010 | Medium | community.general exposes Keycloak credentials in verbose output | Upgrade to community.general >= 12.2.0 |
392| CVE-2025-49520 | High | EDA authenticated argument injection in Git URL (command execution) | Patch AAP/EDA |
393| CVE-2025-49521 | High | EDA template injection via Git branch/refspec (command execution) | Patch AAP/EDA |
394395### Supply chain
396397- Galaxy has no package signing or hash verification. Academic research (2025) found 45 vulnerable dependency chains across 482 Galaxy repos, with 38-54% code overlap propagating vulnerabilities.
398- Pin collection versions in `requirements.yml`. Prefer Automation Hub (Red Hat certified) over Galaxy for production-critical collections.
399- Pin GitHub Actions to commit SHAs in CI/CD (not mutable tags).
400- Scan EE images for CVEs like any container image.
401402### AI-generated playbook risks
403404- AI tools hallucinate module names and parameters. Verify every module exists in the target collection version.
405- AI rarely adds `no_log: true` to secret-handling tasks.
406- AI generates non-idempotent `command`/`shell` tasks where modules exist.
407- AI uses bare module names instead of FQCNs.
408- **Slopsquatting**: AI may suggest Galaxy roles or collections that don't exist. Verify on Galaxy before adding to `requirements.yml`.
409410---
411412## Reference Files
413414- `references/playbook-patterns.md` - playbook and task patterns for common automation work
415- `references/roles-and-collections.md` - role anatomy, collection structure, Galaxy patterns, and Molecule workflows
416- `references/operations-and-execution.md` - inventory layout, ansible.cfg, execution environments, CI/CD integration, and navigator usage
417- `references/vault-and-secrets.md` - Vault usage, secret handling, and external secret-manager integration
418- `references/compliance.md` - PCI-DSS and CIS-oriented hardening guidance
419420---
421422## Output Contract
423424See `skills/_shared/output-contract.md` for the full contract.
425426- **Skill name:** ANSIBLE
427- **Deliverable bucket:** `audits`
428- **Mode:** conditional. When invoked to **analyze, review, audit, or improve** existing repo content, emit the full contract - boxed inline header, body summary inline plus per-finding detail in the deliverable file, boxed conclusion, conclusion table - and write the deliverable to `docs/local/audits/ansible/<YYYY-MM-DD>-<slug>.md`. When invoked to **answer a question, teach a concept, build a new artifact, or generate content**, respond freely without the contract.
429- **Severity scale:** `P0 | P1 | P2 | P3 | info` (see shared contract; only used in audit/review mode).
430431## Related Skills
432433- **terraform** - provisions infrastructure (VMs, networks, cloud resources). Ansible configures
434 what Terraform creates. Day-1 provisioning = terraform; day-2 configuration = ansible.
435- **kubernetes** - for K8s manifests, Helm charts, cluster architecture. Ansible can deploy to
436 K8s via `kubernetes.core` collection, but manifest design belongs in the kubernetes skill.
437- **docker** - for Dockerfile and Compose patterns. Ansible can manage containers via
438 `community.docker`, but image building and Compose design belong in the docker skill.
439- **databases** - for engine configuration (postgresql.conf, pg_hba.conf). Ansible automates
440 the deployment of those configs; databases skill owns the tuning decisions.
441- **ci-cd** - for pipeline design. Ansible can be called from CI/CD pipelines, but pipeline
442 structure (stages, jobs, caching) belongs in the ci-cd skill.
443- **security-audit** - for auditing Ansible playbooks for credential exposure, vault misuse,
444 or supply chain risks in Galaxy dependencies.
445- **debian-ubuntu** - for Debian/Ubuntu/Mint OS-level admin questions outside an automation context.
446- **rhel-fedora** - for RHEL/Fedora/CentOS OS-level admin questions outside an automation context.
447- **kali-linux** - for Kali Linux administration outside an automation context.
448- **arch-btw** - for Arch Linux / CachyOS OS-level admin questions outside an automation context.
449450---
451452## Rules
453454These are non-negotiable. Violating any of these is a bug.
4554561. **FQCNs everywhere.** `ansible.builtin.copy`, not `copy`. No exceptions.
4572. **Idempotent by default.** Every task must be safe to run multiple times. `command`/`shell` tasks need `creates`/`removes` or `changed_when`.
4583. **`no_log: true` on secrets.** Every task handling passwords, tokens, API keys, or sensitive data. CVE-2024-8775 proved the cost of forgetting this.
4594. **No `command`/`shell` when a module exists.** Modules are idempotent, tested, and portable. Shell commands are none of those.
4605. **Variables over hardcoded values.** IPs, paths, package versions, usernames, ports - all variables with defaults.
4616. **Quote Jinja2 variables.** `"{{ var }}"`, not `{{ var }}`. Bare braces break YAML parsing.
4627. **Vault for secrets.** Not plaintext in `group_vars`, not `ansible_ssh_pass` in inventory, not environment variables in playbooks.
4638. **Test with Molecule.** Every role gets a Molecule scenario with converge + idempotence check + verification.
4649. **Pin collection versions.** In `requirements.yml` and EE definitions. Unpinned collections are a supply chain risk.
46510. **`ansible-lint` clean.** Production profile. In CI. On every change.
46611. **Separate inventory per environment.** Production, staging, dev. Never a single inventory with `--limit` for environment selection.
46712. **`--check --diff` before apply.** Review what will change before applying, especially in CI/CD.
46813. **Run the AI self-check.** Every generated playbook gets verified against the checklist above before returning.
Run npx skillmds add majiayu000/ansible-2 in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
· Write/review Ansible playbooks, roles, inventories, Vault, Molecule, AWX/AAP. Triggers: 'ansible', 'playbook', 'role', 'inventory', 'group_vars', 'ansible-lint'. It is listed under DevOps & Infra on SkillMD.
This skill has not completed SkillMD's automated safety review yet. Capability flags: reads secrets. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free. This skill is licensed under MIT.
majiayu000 (@majiayu000) published this skill. Their other Agent Skills are listed on their SkillMD profile.