ansible-coder

This skill guides writing Ansible playbooks for server configuration. Use when hardening servers, installing packages, or automating post-provisioning tasks that cloud-init cannot handle.

16 stars

Best use case

ansible-coder is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

This skill guides writing Ansible playbooks for server configuration. Use when hardening servers, installing packages, or automating post-provisioning tasks that cloud-init cannot handle.

Teams using ansible-coder should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/ansible-coder/SKILL.md --create-dirs "https://raw.githubusercontent.com/diegosouzapw/awesome-omni-skill/main/skills/devops/ansible-coder/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/ansible-coder/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How ansible-coder Compares

Feature / Agentansible-coderStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

This skill guides writing Ansible playbooks for server configuration. Use when hardening servers, installing packages, or automating post-provisioning tasks that cloud-init cannot handle.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Ansible Coder

## ⚠️ SIMPLICITY FIRST - Default to Flat Structure

**ALWAYS start with the simplest approach. Only add complexity when explicitly requested.**

### Simple (DEFAULT) vs Overengineered

| Aspect | ✅ Simple (Default) | ❌ Overengineered |
|--------|---------------------|-------------------|
| Playbooks | 1 playbook with inline tasks | Multiple playbooks + custom roles |
| Roles | Use Galaxy roles (geerlingguy.*) | Write custom roles for simple tasks |
| Inventory | Single `hosts.ini` | Multiple inventories + group_vars hierarchy |
| Variables | Inline in playbook or single vars file | Scattered across group_vars/host_vars |
| File count | ~3-5 files total | 20+ files in nested directories |

### When to Use Simple Approach (90% of cases)

- Setting up 1-5 servers
- Standard stack (Docker, nginx, fail2ban, ufw)
- Single environment or identical servers
- No complex conditional logic per host

### When Complexity is Justified (10% of cases)

- Large fleet with divergent configurations
- Multi-team requiring role isolation
- Complex orchestration with dependencies
- User explicitly requests modular structure

**Rule: If you can fit everything in one 200-line playbook, DO IT.**

## When to Use Ansible vs Cloud-Init

| Use Cloud-Init When | Use Ansible When |
|---------------------|------------------|
| First boot only | Re-running config on existing servers |
| Simple package install | Complex multi-step configuration |
| Basic user creation | Role-based configuration |
| Immutable infrastructure | Mutable servers needing updates |

**Rule of thumb:** Cloud-init for initial provisioning, Ansible for ongoing management.

## Directory Structure

### Simple Structure (DEFAULT)

```
infra/ansible/
├── playbook.yml          # Single playbook with all tasks inline
├── requirements.yml      # Galaxy dependencies (geerlingguy.*, etc.)
├── hosts.ini             # Inventory (git-ignored)
└── hosts.ini.example     # Inventory template
```

### Complex Structure (only when justified)

```
infra/ansible/
├── playbook.yml          # Main playbook
├── requirements.yml      # Galaxy dependencies
├── hosts.ini             # Inventory (git-ignored)
├── hosts.ini.example     # Inventory template
├── group_vars/
│   └── all.yml           # Shared variables
└── roles/
    └── custom_role/
        ├── tasks/main.yml
        ├── handlers/main.yml
        └── templates/
```

## Inventory

### Static Inventory

```ini
# hosts.ini
[web]
192.168.1.1 ansible_user=root

[db]
192.168.1.2 ansible_user=root

[all:vars]
ansible_python_interpreter=/usr/bin/python3
```

### Dynamic from Terraform

```bash
# Generate inventory from Terraform output
SERVER_IP=$(cd infra && tofu output -raw server_ip)
cat > infra/ansible/hosts.ini << EOF
[web]
$SERVER_IP ansible_user=root
EOF
```

## Playbook Structure

### Basic Playbook

```yaml
---
- name: Configure web servers
  hosts: web
  become: true

  vars:
    timezone: "UTC"
    swap_size_mb: "2048"

  tasks:
    - name: Update apt cache
      ansible.builtin.apt:
        update_cache: true
        cache_valid_time: 3600

    - name: Install packages
      ansible.builtin.apt:
        name:
          - docker.io
          - fail2ban
          - ufw
        state: present
```

### With Roles

```yaml
---
- name: Configure web servers
  hosts: web
  become: true

  vars:
    security_autoupdate_reboot: true
    security_autoupdate_reboot_time: "03:00"

  roles:
    - role: geerlingguy.swap
      when: ansible_swaptotal_mb < 1
    - role: geerlingguy.docker
    - role: security
```

## Common Tasks

### Package Management

```yaml
- name: Install required packages
  ansible.builtin.apt:
    name:
      - curl
      - ca-certificates
      - gnupg
      - fail2ban
      - ufw
      - ntp
    state: present
    update_cache: true
```

### Docker Installation

```yaml
- name: Check if Docker is installed
  ansible.builtin.command: docker --version
  register: docker_installed
  ignore_errors: true
  changed_when: false

- name: Install Docker via convenience script
  ansible.builtin.shell: curl -fsSL https://get.docker.com | sh
  when: docker_installed.rc != 0
  args:
    creates: /usr/bin/docker

- name: Ensure Docker is running
  ansible.builtin.systemd:
    name: docker
    state: started
    enabled: true
```

### SSH Hardening

```yaml
- name: Disable SSH password authentication
  ansible.builtin.lineinfile:
    path: /etc/ssh/sshd_config
    regexp: "^#?PasswordAuthentication"
    line: "PasswordAuthentication no"
  notify: Restart ssh

- name: Disable SSH root login with password
  ansible.builtin.lineinfile:
    path: /etc/ssh/sshd_config
    regexp: "^#?PermitRootLogin"
    line: "PermitRootLogin prohibit-password"
  notify: Restart ssh

handlers:
  - name: Restart ssh
    ansible.builtin.systemd:
      name: ssh  # Ubuntu uses 'ssh', not 'sshd'
      state: restarted
```

### Fail2ban

```yaml
- name: Configure fail2ban for SSH
  ansible.builtin.copy:
    dest: /etc/fail2ban/jail.local
    content: |
      [sshd]
      enabled = true
      port = ssh
      filter = sshd
      logpath = /var/log/auth.log
      maxretry = 5
      bantime = 3600
      findtime = 600
    mode: "0644"
  notify: Restart fail2ban

- name: Ensure fail2ban is running
  ansible.builtin.systemd:
    name: fail2ban
    state: started
    enabled: true

handlers:
  - name: Restart fail2ban
    ansible.builtin.systemd:
      name: fail2ban
      state: restarted
```

### UFW Firewall

```yaml
- name: Set UFW default policies
  community.general.ufw:
    direction: "{{ item.direction }}"
    policy: "{{ item.policy }}"
  loop:
    - { direction: incoming, policy: deny }
    - { direction: outgoing, policy: allow }

- name: Allow specified ports through UFW
  community.general.ufw:
    rule: allow
    port: "{{ item }}"
    proto: tcp
  loop:
    - 22   # SSH
    - 80   # HTTP
    - 443  # HTTPS

- name: Enable UFW
  community.general.ufw:
    state: enabled
```

### Kernel Tuning

```yaml
- name: Configure sysctl for performance
  ansible.posix.sysctl:
    name: "{{ item.name }}"
    value: "{{ item.value }}"
    state: present
    reload: true
  loop:
    - { name: vm.swappiness, value: "10" }
    - { name: net.core.somaxconn, value: "65535" }
```

### Timezone

```yaml
- name: Set timezone
  community.general.timezone:
    name: "{{ timezone }}"
```

### Remove Snap (Ubuntu bloat)

```yaml
- name: Remove snapd
  ansible.builtin.apt:
    name: snapd
    state: absent
    purge: true
  ignore_errors: true

- name: Remove snap directories
  ansible.builtin.file:
    path: "{{ item }}"
    state: absent
  loop:
    - /snap
    - /var/snap
    - /var/lib/snapd
```

## Galaxy Dependencies

### requirements.yml

```yaml
---
roles:
  - name: geerlingguy.swap
    version: 2.0.0
  - name: geerlingguy.docker
    version: 7.4.1

collections:
  - name: community.general
  - name: ansible.posix
```

### Installation

```bash
ansible-galaxy install -r requirements.yml --force
```

## Running Playbooks

### Basic Execution

```bash
ANSIBLE_HOST_KEY_CHECKING=False ansible-playbook -i hosts.ini playbook.yml
```

### With Variables

```bash
ansible-playbook -i hosts.ini playbook.yml \
  -e "timezone=Europe/Berlin" \
  -e "swap_size_mb=4096"
```

### Dry Run

```bash
ansible-playbook -i hosts.ini playbook.yml --check --diff
```

### Limit to Specific Hosts

```bash
ansible-playbook -i hosts.ini playbook.yml --limit web
```

## Kamal Server Preparation

Complete playbook for Kamal deployment servers (based on [kamal-ansible-manager](https://github.com/guillaumebriday/kamal-ansible-manager)):

```yaml
---
- name: Prepare server for Kamal deployment
  hosts: web
  become: true

  vars:
    swap_file_size_mb: "2048"
    timezone: "UTC"
    ufw_allowed_ports: [22, 80, 443]

  roles:
    - role: geerlingguy.swap
      when: ansible_swaptotal_mb < 1

  tasks:
    # System updates
    - name: Update and upgrade packages
      ansible.builtin.apt:
        update_cache: true
        upgrade: dist

    # Remove bloat
    - name: Remove snapd
      ansible.builtin.apt:
        name: snapd
        state: absent
        purge: true
      ignore_errors: true

    # Essential packages
    - name: Install required packages
      ansible.builtin.apt:
        name: [curl, ca-certificates, fail2ban, ufw, ntp]
        state: present

    # Docker
    - name: Install Docker
      ansible.builtin.shell: curl -fsSL https://get.docker.com | sh
      args:
        creates: /usr/bin/docker

    - name: Enable Docker
      ansible.builtin.systemd:
        name: docker
        state: started
        enabled: true

    # Security
    - name: Configure fail2ban
      ansible.builtin.copy:
        dest: /etc/fail2ban/jail.local
        content: |
          [sshd]
          enabled = true
          maxretry = 5
          bantime = 3600
        mode: "0644"
      notify: Restart fail2ban

    - name: Configure UFW
      community.general.ufw:
        rule: allow
        port: "{{ item }}"
        proto: tcp
      loop: "{{ ufw_allowed_ports }}"

    - name: Enable UFW
      community.general.ufw:
        state: enabled
        policy: deny
        direction: incoming

    # SSH hardening
    - name: Harden SSH
      ansible.builtin.lineinfile:
        path: /etc/ssh/sshd_config
        regexp: "{{ item.regexp }}"
        line: "{{ item.line }}"
      loop:
        - { regexp: "^#?PasswordAuthentication", line: "PasswordAuthentication no" }
        - { regexp: "^#?PermitRootLogin", line: "PermitRootLogin prohibit-password" }
      notify: Restart ssh

    # Performance
    - name: Tune kernel
      ansible.posix.sysctl:
        name: "{{ item.name }}"
        value: "{{ item.value }}"
        reload: true
      loop:
        - { name: vm.swappiness, value: "10" }
        - { name: net.core.somaxconn, value: "65535" }

    - name: Set timezone
      community.general.timezone:
        name: "{{ timezone }}"

  handlers:
    - name: Restart fail2ban
      ansible.builtin.systemd:
        name: fail2ban
        state: restarted

    - name: Restart ssh
      ansible.builtin.systemd:
        name: ssh
        state: restarted
```

## Integration with Terraform

### Provision Script Pattern

```bash
#!/usr/bin/env bash
# infra/bin/provision

# 1. Terraform creates server
cd infra && tofu apply
SERVER_IP=$(tofu output -raw server_ip)

# 2. Wait for SSH
until ssh -o ConnectTimeout=5 root@$SERVER_IP true 2>/dev/null; do
  sleep 5
done

# 3. Generate inventory
echo "[web]\n$SERVER_IP ansible_user=root" > ansible/hosts.ini

# 4. Run Ansible
cd ansible
ansible-galaxy install -r requirements.yml
ANSIBLE_HOST_KEY_CHECKING=False ansible-playbook -i hosts.ini playbook.yml

# 5. Kamal bootstrap
cd ../..
bundle exec kamal server bootstrap
```

## Troubleshooting

| Issue | Cause | Fix |
|-------|-------|-----|
| `ssh: connect refused` | Server not ready | Wait or check firewall |
| `Permission denied` | Wrong SSH key | Specify with `-i` |
| `sudo: password required` | User needs NOPASSWD | Use `become_method: sudo` |
| Handler not running | Task didn't change | Use `changed_when: true` |
| Module not found | Missing collection | Install from requirements.yml |

Related Skills

codereadr-automation

16
from diegosouzapw/awesome-omni-skill

Automate Codereadr tasks via Rube MCP (Composio). Always search tools first for current schemas.

ansible-generator

16
from diegosouzapw/awesome-omni-skill

Comprehensive toolkit for generating best practice Ansible playbooks, roles, tasks, and inventory files.

aasm-coder

16
from diegosouzapw/awesome-omni-skill

Implement state machines with AASM for workflow management. Covers state transitions, guards, callbacks, and testing.

coder-docs

16
from diegosouzapw/awesome-omni-skill

Index + offline snapshot of coder/coder documentation (progressive disclosure).

coder-hahomelabs

16
from diegosouzapw/awesome-omni-skill

Coder workspace environment for hahomelabs.com deployments. Includes networking, ports, convex config, nhost config

ansible

16
from diegosouzapw/awesome-omni-skill

Provides comprehensive guidance for Ansible automation including playbooks, roles, inventory, and module usage. Use when the user asks about Ansible, needs to automate IT tasks, create Ansible playbooks, or manage infrastructure with Ansible.

ansible-workflow

16
from diegosouzapw/awesome-omni-skill

Ansible automation workflow guidelines. Activate when working with Ansible playbooks, ansible-playbook, inventory files (.yml, .ini), or Ansible-specific patterns.

ansible-validator

16
from diegosouzapw/awesome-omni-skill

Comprehensive toolkit for validating, linting, testing, and automating Ansible playbooks, roles, and collections. Use this skill when working with Ansible files (.yml, .yaml playbooks, roles, inventories), validating automation code, debugging playbook execution, performing dry-run testing with check mode, or working with custom modules and collections.

ansible-testinfra

16
from diegosouzapw/awesome-omni-skill

Bootstrap minimal testinfra pytest suite for an Ansible role and remind to run via uv

ansible-roles

16
from diegosouzapw/awesome-omni-skill

Use when structuring and reusing code with Ansible roles for modular, maintainable automation and configuration management.

ansible-role-init

16
from diegosouzapw/awesome-omni-skill

Scaffold a new Ansible role via ansible-galaxy init

ansible-playbooks

16
from diegosouzapw/awesome-omni-skill

Use when writing and organizing Ansible playbooks for automated configuration management and infrastructure orchestration.