---
trigger: glob
globs: **/*.yml,**/*.yaml,roles/**,playbooks/**
---

# Ansible

## Commands
- Lint: `ansible-lint` · Syntax: `ansible-playbook site.yml --syntax-check`
- Dry run: `ansible-playbook -i inventories/staging site.yml --check --diff --limit <host>`
- **Never run a playbook without `--check` against real inventories unless the user asks.** Molecule tests (`molecule test`) are fine.

## Rules
- Use fully qualified module names (`ansible.builtin.copy`, `ansible.builtin.template`, `community.general.ufw`).
- Prefer modules over `command`/`shell`. If you must use them, add `creates:`/`removes:` or `changed_when:`/`failed_when:` so they're idempotent and report changes honestly.
- Every task has a `name:` starting with a verb ("Install nginx").
- Roles follow the standard layout (`tasks/`, `handlers/`, `templates/`, `defaults/main.yml`, `meta/main.yml`). Defaults hold overridable variables, prefixed with the role name (`nginx_worker_processes`).
- Restart services via handlers (`notify: Restart nginx`), not unconditional restart tasks.
- Templates (`.j2`) start with `{{ ansible_managed | comment }}`.
- Secrets in Ansible Vault (`ansible-vault encrypt_string`) or an external secret lookup; `no_log: true` on tasks that handle them.
- Use `become: true` only on tasks that need it.
- Pin collections in `collections/requirements.yml`.
- Keep inventories per environment (`inventories/staging`, `inventories/production`) with `group_vars`/`host_vars`.

## Done
`ansible-lint` passes with the production profile and a `--check --diff` run on staging shows only expected changes.
