Skip to content
Back to skills

Ansible Playbook Design

ASecurity

Patterns for designing well-structured, maintainable Ansible playbooks with state-based (present/absent) design, play organization, and proper variable scoping.

  • 2 stars
  • 0 votes
  • 0 copies
  • 10 views
  • Added September 11, 2026
devopsbashnodedockerapi

Works with

  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned September 11, 2026

npx -y skills add jcasnellie69/homelab-config --skill ansible-playbook-design --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Ansible Playbook Design?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Ansible Playbook Design
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/jcasnellie69-ansible-playbook-design/badge)](https://www.skillsdirectory.com/skills/jcasnellie69-ansible-playbook-design)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: ansible-playbook-design
description: >
  Patterns for designing well-structured, maintainable Ansible playbooks with
  state-based (present/absent) design, play organization, and proper variable scoping.
when_to_use: >
  Use when creating new Ansible playbooks, designing playbook structure, implementing
  state-based playbooks with present/absent patterns, organizing plays and tasks, or
  structuring playbook variables.
---

# Ansible Playbook Design

Patterns for designing well-structured, maintainable Ansible playbooks.

## State-Based Playbook Pattern

Design playbooks to handle both creation and removal via a `state` variable.

### Core Pattern

```yaml
---
- name: Manage admin user account
  hosts: all
  become: true

  vars:
    admin_state: present  # or absent

  tasks:
    - name: Create admin user
      ansible.builtin.user:
        name: "{{ admin_name }}"
        groups: "{{ admin_groups }}"
        state: "{{ admin_state }}"

    - name: Configure SSH key
      ansible.posix.authorized_key:
        user: "{{ admin_name }}"
        key: "{{ admin_ssh_key }}"
        state: "{{ admin_state }}"
      when: admin_state == 'present'
```

### Usage

```bash
# Create user (default)
uv run ansible-playbook playbooks/manage-admin.yml \
  -e "admin_name=alice" \
  -e "admin_ssh_key='ssh-ed25519 AAAA...'"

# Remove user
uv run ansible-playbook playbooks/manage-admin.yml \
  -e "admin_name=alice" \
  -e "admin_state=absent"
```

### Benefits

- Single source of truth
- Consistent interface
- Less code duplication
- Follows community role conventions

## Play Structure

### Recommended Play Sections

Order sections consistently across all playbooks:

```yaml
---
- name: Descriptive play name
  hosts: target_group
  become: true
  gather_facts: true

  vars:
    # Play-level variables
    app_version: "2.0.0"

  vars_files:
    # External variable files
    - vars/secrets.yml

  pre_tasks:
    # Tasks that must run before roles
    - name: Update apt cache
      ansible.builtin.apt:
        update_cache: true
        cache_valid_time: 3600

  roles:
    # Role includes
    - role: common
    - role: app_deploy
      vars:
        deploy_version: "{{ app_version }}"

  tasks:
    # Play-specific tasks
    - name: Verify deployment
      ansible.builtin.uri:
        url: http://localhost:8080/health

  post_tasks:
    # Cleanup or finalization
    - name: Send deployment notification
      ansible.builtin.debug:
        msg: "Deployment complete"

  handlers:
    # Event-triggered tasks
    - name: restart app
      ansible.builtin.systemd:
        name: myapp
        state: restarted
```

## Variable Organization

### Variable Precedence (Key Levels)

From lowest to highest precedence:

1. Role defaults (`roles/x/defaults/main.yml`)
2. Inventory group_vars (`group_vars/all.yml`)
3. Inventory host_vars (`host_vars/hostname.yml`)
4. Play vars (`vars:` in playbook)
5. Task vars (`vars:` on task)
6. Extra vars (`-e` on command line) - **highest**

### Organizing Variables

```text
ansible/
├── group_vars/
│   ├── all.yml           # Variables for ALL hosts
│   ├── proxmox.yml       # Proxmox cluster hosts
│   └── docker_hosts.yml  # Docker host group
├── host_vars/
│   ├── node01.yml        # Host-specific overrides
│   └── node02.yml
└── playbooks/
    └── deploy.yml        # Uses vars: for playbook-specific
```

### Variable Naming by Scope

```yaml
# group_vars/all.yml - Global defaults
default_timezone: "UTC"
ntp_servers:
  - 0.pool.ntp.org
  - 1.pool.ntp.org

# group_vars/proxmox.yml - Group-specific
proxmox_api_host: "192.168.1.10"
proxmox_cluster_name: "production"

# host_vars/node01.yml - Host-specific overrides
proxmox_node_id: 1
ceph_osd_devices:
  - /dev/sdb
  - /dev/sdc
```

## Task Organization with Includes

### When to Split Tasks

Split playbook tasks into separate files when:

- Tasks exceed 50 lines
- Logical groupings emerge (networking, storage, users)
- Conditional sections can be skipped entirely

### Include Patterns

```yaml
# playbooks/setup-cluster.yml
---
- name: Setup Proxmox cluster
  hosts: proxmox
  become: true

  tasks:
    - name: Configure networking
      ansible.builtin.include_tasks: tasks/networking.yml

    - name: Setup storage
      ansible.builtin.include_tasks: tasks/storage.yml
      when: setup_storage | default(true)

    - name: Initialize cluster
      ansible.builtin.include_tasks: tasks/cluster-init.yml
      when: inventory_hostname == groups['proxmox'][0]
```

### import_tasks vs include_tasks

| Feature | import_tasks | include_tasks |
|---------|--------------|---------------|
| When evaluated | Parse time (static) | Runtime (dynamic) |
| Supports loops | No | Yes |
| Supports conditionals on import | Limited | Full |
| Use case | Ordered execution | Conditional/looped |

```yaml
# Static import - always loaded, order matters
- ansible.builtin.import_tasks: users.yml
- ansible.builtin.import_tasks: permissions.yml

# Dynamic include - conditional, looped
- ansible.builtin.include_tasks: "setup-{{ ansible_os_family }}.yml"
- ansible.builtin.include_tasks: deploy-app.yml
  loop: "{{ applications }}"
```

## Multi-Play Playbooks

Use multiple plays for different host groups or privilege levels:

```yaml
---
# Play 1: Gather facts from all nodes
- name: Gather cluster information
  hosts: proxmox
  gather_facts: true
  tasks:
    - name: Set cluster facts
      ansible.builtin.set_fact:
        cluster_node_count: "{{ groups['proxmox'] | length }}"

# Play 2: Initialize primary node
- name: Initialize cluster on primary
  hosts: proxmox[0]
  become: true
  tasks:
    - name: Create cluster
      ansible.builtin.command: pvecm create {{ cluster_name }}
      when: not cluster_exists

# Play 3: Join secondary nodes
- name: Join cluster on secondary nodes
  hosts: proxmox[1:]
  become: true
  serial: 1  # One node at a time
  tasks:
    - name: Join cluster
      ansible.builtin.command: pvecm add {{ primary_node }}
      when: not node_in_cluster
```

## Handler Best Practices

### Define Handlers at Play Level

```yaml
---
- name: Configure web server
  hosts: webservers
  become: true

  tasks:
    - name: Update nginx config
      ansible.builtin.template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
      notify: reload nginx

    - name: Update SSL certificates
      ansible.builtin.copy:
        src: "{{ item }}"
        dest: /etc/nginx/ssl/
      loop:
        - cert.pem
        - key.pem
      notify: reload nginx

  handlers:
    - name: reload nginx
      ansible.builtin.systemd:
        name: nginx
        state: reloaded
```

### Handler Execution Order

Handlers run:

1. At the end of each play
2. In the order they are defined (not notified)
3. Only once, even if notified multiple times

Force immediate handler execution:

```yaml
- name: Update critical config
  ansible.builtin.template:
    src: config.j2
    dest: /etc/app/config.yml
  notify: restart app

- name: Flush handlers now
  ansible.builtin.meta: flush_handlers

- name: Verify app is running
  ansible.builtin.uri:
    url: http://localhost:8080/health
```

## Playbook Validation

### Pre-flight Checks

Add validation at the start of playbooks:

```yaml
---
- name: Deploy application
  hosts: app_servers
  become: true

  tasks:
    - name: Validate required variables
      ansible.builtin.assert:
        that:
          - app_version is defined
          - app_version | regex_search('^\d+\.\d+\.\d+$')
          - deploy_env in ['staging', 'production']
        fail_msg: "Invalid configuration. Check app_version and deploy_env."

    - name: Check disk space
      ansible.builtin.assert:
        that: ansible_mounts | selectattr('mount', 'equalto', '/') | map(attribute='size_available') | first > 1073741824
        fail_msg: "Insufficient disk space. Need at least 1GB free."
```

## Template Patterns

### Playbook Template Structure

```yaml
---
# playbooks/template-playbook.yml
# Description: [What this playbook does]
# Usage: uv run ansible-playbook playbooks/template-playbook.yml -e "var=value"
# Requirements: [Any prerequisites]

- name: [Descriptive play name]
  hosts: [target_group]
  become: [true/false]
  gather_facts: [true/false]

  vars:
    # Configurable variables with defaults
    resource_state: present

  tasks:
    - name: Validate inputs
      ansible.builtin.assert:
        that:
          - required_var is defined
        fail_msg: "required_var must be defined"

    # Main tasks...

    - name: Verify completion
      ansible.builtin.debug:
        msg: "Playbook completed successfully"
```

## Additional Resources

For detailed playbook patterns and techniques, consult:

- **`references/playbook-role-patterns.md`** - Comprehensive playbook organization patterns, play structure, import strategies

## Related Skills

- **ansible-role-design** - When to use roles vs playbooks
- **ansible-fundamentals** - Core module selection and naming
- **ansible-error-handling** - Block/rescue patterns in playbooks

Files in this skill

  • SKILL.md8.9 KB
  • references/playbook-role-patterns.md9.5 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.