Expert guidance for writing clean, maintainable Ansible playbooks following industry conventions and best practices
Expert guidance for writing clean, maintainable Ansible playbooks and roles following industry conventions and best practices.
When working with Ansible YAML files (playbooks, roles, tasks, vars), follow these conventions:
1. **Simplicity First**: Use advanced Ansible features only when necessary. Keep configurations straightforward and maintainable.
2. **Version Control**: Always store Ansible configurations in version control (Git).
3. **Task Naming**: Give every play, block, and task a concise, descriptive `name`:
- Start with an action verb (Install, Configure, Copy, Ensure, Create, etc.)
- Capitalize the first letter
- Omit trailing periods
- Omit role name from role tasks (Ansible displays it automatically)
- Example: `Install nginx package` not `install nginx.` or `role_name : Install nginx`
4. **Comments**: Add comments to explain **what**, **how**, or **why** for complex logic. Avoid redundant comments that merely repeat the code.
5. **Idempotency**: Use idempotent modules whenever possible. Avoid `shell`, `command`, and `raw` modules that break idempotency:
- If `shell`/`command` is required, use `creates:` or `removes:` parameters to prevent unnecessary execution
- Prefer native Ansible modules over shell commands
6. **Fully Qualified Collection Names (FQCN)**: Always use FQCN to ensure correct module/plugin selection:
- Use `ansible.builtin` for builtin modules: `ansible.builtin.copy`, `ansible.builtin.service`, etc.
- Example: `ansible.builtin.package:` not `package:`
7. **Explicit State**: For modules with optional `state` parameter, always specify `state: present` or `state: absent` for clarity.
8. **Least Privilege**: Only use `become: true` when super user privileges are required:
- Set at play level only if all tasks need elevation
- Otherwise, set at task level for specific tasks
1. **Dynamic Inventory**: Use dynamic inventory for cloud resources with tags for grouping (environment, function, location).
2. **Group Variables**: Use `group_vars` to set variables based on dynamic groups.
3. **Variable Naming**: Use `snake_case` for all variable names.
4. **Variable Sorting**: Sort variables alphabetically in `vars:` maps or variable files.
**Ansible-only projects**:
1. Create `group_vars/<group_name>/vars` for all variables (including sensitive ones)
2. Create `group_vars/<group_name>/vault` for sensitive variables prefixed with `vault_`
3. In `vars` file, reference vault variables: `db_password: "{{ vault_db_password }}"`
4. Encrypt the `vault` file with `ansible-vault encrypt`
5. Use variable names from `vars` file in playbooks
**Multi-tool projects** (Terraform + Ansible):
1. **Indentation**: Use 2 spaces. Always indent lists.
2. **Blank Lines**: Add single blank line between:
- Two host blocks
- Two task blocks
- Host and include blocks
3. **Map Syntax**: Always use multi-line map syntax (even for single key-value pairs):
```yaml
# Good
ansible.builtin.package:
name: nginx
state: present
# Bad
ansible.builtin.package: name=nginx state=present
```
4. **Quotes**: Prefer single quotes over double quotes:
- Use double quotes only when nested in single quotes or for escape sequences (`\n`)
- For long strings, use folded `>` or literal `|` block scalars without quotes
5. **Play Structure Order**:
```yaml
- hosts: webservers
become: true
remote_user: ansible
vars:
app_port: 8080
pre_tasks:
- name: Update apt cache
roles:
- common
tasks:
- name: Install application
```
6. **Task Structure Order**:
```yaml
- name: Install and start nginx
ansible.builtin.package:
name: nginx
state: present
loop: "{{ packages }}"
become: true
register: install_result
tags:
- packages
- webserver
```
7. **Include Statements**: Quote filenames. Only add blank lines between multi-line includes:
```yaml
- include_tasks: 'setup.yml'
- include_tasks: 'configure.yml'
tags: configure
- include_tasks: 'deploy.yml'
tags: deploy
```
Before committing, run:
1. `ansible-lint` - Check best practices and style
2. `yamllint` - Validate YAML syntax
3. `ansible-playbook --syntax-check` - Check for syntax errors
4. `ansible-playbook --check --diff` - Dry-run to preview changes
```yaml
---
become: true
vars:
app_name: myapp
app_port: 8080
ssl_enabled: true
pre_tasks:
- name: Update apt cache
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600
when: ansible_os_family == 'Debian'
roles:
- common
- security
tasks:
- name: Install nginx web server
ansible.builtin.package:
name: nginx
state: present
- name: Copy application configuration
ansible.builtin.template:
src: 'app.conf.j2'
dest: '/etc/nginx/sites-available/{{ app_name }}.conf'
owner: root
group: root
mode: '0644'
notify: Reload nginx
- name: Ensure nginx is running
ansible.builtin.service:
name: nginx
state: started
enabled: true
handlers:
- name: Reload nginx
ansible.builtin.service:
name: nginx
state: reloaded
```
Leave a review
No reviews yet. Be the first to review this skill!
# Download SKILL.md from killerskills.ai/api/skills/ansible-best-practices/raw