ansible-role-design
This skill should be used when creating Ansible roles, designing role directory structure, organizing role variables in defaults vs vars, writing role handlers, or structuring role tasks. Based on analysis of 7 production geerlingguy roles.
$ Installieren
git clone https://github.com/basher83/lunar-claude /tmp/lunar-claude && cp -r /tmp/lunar-claude/plugins/infrastructure/ansible-workflows/skills/ansible-role-design ~/.claude/skills/lunar-claude// tip: Run this command in your terminal to install the skill
name: ansible-role-design description: > This skill should be used when creating Ansible roles, designing role directory structure, organizing role variables in defaults vs vars, writing role handlers, or structuring role tasks. Based on analysis of 7 production geerlingguy roles.
Ansible Role Design
Production-grade role structure patterns derived from analysis of 7 geerlingguy roles.
Standard Directory Structure
Every Ansible role follows this organizational pattern:
role-name/
âââ defaults/
â âââ main.yml # User-configurable defaults (lowest precedence)
âââ vars/
â âââ Debian.yml # OS-specific internal values
â âââ RedHat.yml
âââ tasks/
â âââ main.yml # Task router
â âââ install.yml # Feature-specific tasks
â âââ configure.yml
âââ handlers/
â âââ main.yml # Event-triggered tasks
âââ templates/
â âââ config.conf.j2 # Jinja2 templates
âââ files/
â âââ static-file.txt # Static files
âââ meta/
â âââ main.yml # Role metadata, dependencies
âââ README.md # Documentation
Directory Purposes
| Directory | Purpose | Precedence |
|---|---|---|
defaults/ | User-overridable values | Lowest |
vars/ | Internal/OS-specific values | High |
tasks/ | Ansible tasks | N/A |
handlers/ | Service restarts, reloads | N/A |
templates/ | Jinja2 config files | N/A |
files/ | Static files to copy | N/A |
meta/ | Galaxy info, dependencies | N/A |
When to Omit Directories
Only create directories that are actually needed:
- Omit
templates/if using onlylineinfileorcopy - Omit
handlers/if role doesn't manage services - Omit
vars/if no OS-specific differences - Omit
files/if no static files to copy
Task Organization
Main Task File as Router
Use tasks/main.yml as a routing file that includes feature-specific files:
# tasks/main.yml
---
- name: Include OS-specific variables
ansible.builtin.include_vars: "{{ ansible_os_family }}.yml"
- name: Install packages
ansible.builtin.include_tasks: install.yml
- name: Configure service
ansible.builtin.include_tasks: configure.yml
- name: Setup users
ansible.builtin.include_tasks: users.yml
when: role_users | length > 0
When to Split Tasks
| Scenario | Approach |
|---|---|
| < 30 lines | Keep in main.yml |
| 30-100 lines | Consider splitting |
| > 100 lines | Definitely split |
| Optional features | Separate file with when: |
| OS-specific logic | Separate files per OS |
Task File Naming
Use descriptive, feature-based names:
tasks/
âââ main.yml # Router only
âââ install.yml # Package installation
âââ configure.yml # Configuration tasks
âââ users.yml # User management
âââ install-Debian.yml # Debian-specific install
âââ install-RedHat.yml # RedHat-specific install
Variable Organization
defaults/ vs vars/
| Location | Purpose | User Override? |
|---|---|---|
defaults/main.yml | User configuration | Yes (easily) |
vars/main.yml | Internal constants | Possible but discouraged |
vars/Debian.yml | OS-specific values | No (internal) |
defaults/main.yml Example
# defaults/main.yml
---
# User-configurable options
docker_edition: "ce"
docker_service_state: started
docker_service_enabled: true
docker_users: []
# Feature toggles
docker_install_compose: true
docker_compose_version: "2.24.0"
vars/Debian.yml Example
# vars/Debian.yml
---
# OS-specific internal values (not for user override)
docker_package_name: docker-ce
docker_service_name: docker
docker_config_path: /etc/docker/daemon.json
Loading OS-Specific Variables
Simple pattern:
- name: Include OS-specific variables
ansible.builtin.include_vars: "{{ ansible_os_family }}.yml"
Advanced pattern with fallback:
- name: Load OS-specific vars
ansible.builtin.include_vars: "{{ lookup('first_found', params) }}"
vars:
params:
files:
- "{{ ansible_distribution }}.yml"
- "{{ ansible_os_family }}.yml"
- main.yml
paths:
- vars
Variable Naming Convention
Prefix variables with role name:
# Pattern: {role_name}_{feature}_{attribute}
# Examples
docker_edition: "ce"
docker_service_state: started
docker_compose_version: "2.24.0"
docker_users: []
# Grouped by feature
security_ssh_port: 22
security_ssh_password_auth: "no"
security_fail2ban_enabled: true
Benefits
- Prevents conflicts with other roles
- Clear ownership of variables
- Easy to grep across codebase
- Self-documenting
Handler Patterns
Simple Handler Definitions
# handlers/main.yml
---
- name: restart docker
ansible.builtin.systemd:
name: docker
state: restarted
- name: reload nginx
ansible.builtin.systemd:
name: nginx
state: reloaded
Handler Naming
Use lowercase with action + service pattern:
- name: restart ssh # Not "Restart SSH Service"
- name: reload nginx # Not "Reload Nginx Config"
- name: reload systemd # For daemon-reload
Throttled Handlers
For cluster operations, restart one node at a time:
- name: restart pve-cluster
ansible.builtin.systemd:
name: pve-cluster
state: restarted
throttle: 1
Template Organization
When to Use Templates
Use templates/ when:
- Configuration has conditional content
- Need variable substitution
- Complex multi-line configuration
- Users may need to extend/override
Use lineinfile when:
- Simple single-line changes
- Modifying existing system files
Template Variables
Expose template paths as variables for user override:
# defaults/main.yml
nginx_conf_template: nginx.conf.j2
nginx_vhost_template: vhost.j2
# tasks/configure.yml
- name: Deploy nginx config
ansible.builtin.template:
src: "{{ nginx_conf_template }}"
dest: /etc/nginx/nginx.conf
notify: reload nginx
Meta Configuration
meta/main.yml Structure
# meta/main.yml
---
galaxy_info:
author: your_name
description: Role description
license: MIT
min_ansible_version: "2.12"
platforms:
- name: Debian
versions:
- bullseye
- bookworm
- name: Ubuntu
versions:
- focal
- jammy
dependencies:
- role: common
- role: geerlingguy.docker
when: install_docker | default(false)
Role Complexity Scaling
Based on geerlingguy role analysis:
| Role Complexity | Directories | Task Files | Examples |
|---|---|---|---|
| Minimal | 3-4 | 1 (main.yml) | pip, git |
| Standard | 5-6 | 2-4 | security, docker |
| Complex | 7+ | 5-8 | postgresql, nginx |
Minimal Role
pip/
âââ defaults/main.yml
âââ tasks/main.yml
âââ meta/main.yml
âââ README.md
Standard Role
docker/
âââ defaults/main.yml
âââ vars/{Debian,RedHat}.yml
âââ tasks/{main,install,configure}.yml
âââ handlers/main.yml
âââ meta/main.yml
âââ README.md
Complex Role
postgresql/
âââ defaults/main.yml
âââ vars/{Debian,RedHat,Archlinux}.yml
âââ tasks/{main,install,configure,users,databases}.yml
âââ handlers/main.yml
âââ templates/{postgresql.conf,pg_hba.conf}.j2
âââ meta/main.yml
âââ README.md
Task Naming Convention
Start task names with action verbs:
# GOOD
- name: Ensure Docker is installed
- name: Configure SSH security settings
- name: Add user to docker group
# BAD
- name: Docker installation
- name: SSH settings
- name: User docker group
File Validation
Validate critical configuration files:
- name: Update SSH configuration
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: "^PermitRootLogin"
line: "PermitRootLogin no"
validate: 'sshd -T -f %s'
notify: restart ssh
- name: Update sudoers
ansible.builtin.lineinfile:
path: /etc/sudoers
line: "{{ user }} ALL=(ALL) NOPASSWD: ALL"
validate: 'visudo -cf %s'
Documentation
Every role needs a README.md with:
- Description - What the role does
- Requirements - Prerequisites
- Role Variables - All variables with defaults
- Dependencies - Other roles needed
- Example Playbook - How to use it
Additional Resources
For detailed role design patterns and techniques, consult:
references/role-structure-standards.md- Production role structure patterns from geerlingguy analysisreferences/handler-best-practices.md- Handler design, notification patterns, flush strategiesreferences/meta-dependencies.md- Role dependencies, Galaxy metadata, platform supportreferences/variable-management-patterns.md- Variable naming, scoping, precedence patternsreferences/documentation-templates.md- README templates and documentation standards
Related Skills
- ansible-playbook-design - When to use roles vs playbooks
- ansible-fundamentals - Module selection and naming
- ansible-testing - Role testing with molecule
Repository
