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.
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
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/ansible-coder/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How ansible-coder Compares
| Feature / Agent | ansible-coder | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/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
Automate Codereadr tasks via Rube MCP (Composio). Always search tools first for current schemas.
ansible-generator
Comprehensive toolkit for generating best practice Ansible playbooks, roles, tasks, and inventory files.
aasm-coder
Implement state machines with AASM for workflow management. Covers state transitions, guards, callbacks, and testing.
coder-docs
Index + offline snapshot of coder/coder documentation (progressive disclosure).
coder-hahomelabs
Coder workspace environment for hahomelabs.com deployments. Includes networking, ports, convex config, nhost config
ansible
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
Ansible automation workflow guidelines. Activate when working with Ansible playbooks, ansible-playbook, inventory files (.yml, .ini), or Ansible-specific patterns.
ansible-validator
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
Bootstrap minimal testinfra pytest suite for an Ansible role and remind to run via uv
ansible-roles
Use when structuring and reusing code with Ansible roles for modular, maintainable automation and configuration management.
ansible-role-init
Scaffold a new Ansible role via ansible-galaxy init
ansible-playbooks
Use when writing and organizing Ansible playbooks for automated configuration management and infrastructure orchestration.