name: ansible-fundamentals description: > Core principles and golden rules for writing production-quality Ansible automation, covering FQCN requirements, module selection guidance, and execution patterns using uv run. when_to_use: > Use when writing Ansible playbooks, creating Ansible tasks, running ansible-playbook commands, selecting Ansible modules, or working with Ansible collections.
Ansible Fundamentals
Core principles and golden rules for writing production-quality Ansible automation.
Golden Rules
These rules apply to ALL Ansible code in this repository:
Use
uv runprefix - Execute all Ansible commands through uv:uv run ansible-playbook playbooks/my-playbook.yml uv run ansible-lint uv run ansible-galaxy collection install -r requirements.ymlFully Qualified Collection Names (FQCN) - Avoid short module names:
# CORRECT - name: Install package ansible.builtin.apt: name: nginx state: present # WRONG - deprecated short names - name: Install package apt: name: nginxControl command/shell modules - Add
changed_whenandfailed_when:- name: Check if service exists ansible.builtin.command: systemctl status myservice register: service_check changed_when: false failed_when: falseUse
set -euo pipefail- In all shell scripts and shell module calls:- name: Run pipeline command ansible.builtin.shell: | set -euo pipefail cat file.txt | grep pattern | wc -l args: executable: /bin/bashTag sensitive tasks - Use
no_log: truefor secrets:- name: Set database password ansible.builtin.command: set-password {{ db_password }} no_log: trueIdempotency first - Check before create, verify after.
Descriptive task names - Start with action verbs (Ensure, Configure, Install, Create).
Module Selection Guide
Decision Matrix
| Need | Use | Why |
|---|---|---|
| Install packages | ansible.builtin.apt/yum/dnf |
Native modules handle state |
| Manage files | ansible.builtin.copy/template/file |
Idempotent by default |
| Edit config lines | ansible.builtin.lineinfile |
Surgical edits, not full replace |
| Run commands | ansible.builtin.command |
When no native module exists |
| Need shell features | ansible.builtin.shell |
Pipes, redirects, globs |
| Manage services | ansible.builtin.systemd/service |
State management built-in |
| Manage users | ansible.builtin.user |
Cross-platform, idempotent |
Prefer Native Modules
Native modules provide:
- Built-in idempotency (no need for
changed_when) - Better error handling
- Cross-platform compatibility
- Clear documentation
# PREFER native module
- name: Create user
ansible.builtin.user:
name: deploy
groups: docker
state: present
# AVOID command when module exists
- name: Create user
ansible.builtin.command: useradd -G docker deploy
# Requires: changed_when, failed_when, idempotency logic
When Command/Shell is Acceptable
Use command or shell modules when:
- No native module exists for the operation
- Interacting with vendor CLI tools (certbot, kubectl, aws)
- Running one-off scripts
Add proper controls:
- name: Enable Apache module
ansible.builtin.command: a2enmod {{ module_name }}
register: mod_result
changed_when: "'already enabled' not in mod_result.stdout"
failed_when:
- mod_result.rc != 0
- "'already enabled' not in mod_result.stdout"
Collections in Use
This repository uses these Ansible collections:
| Collection | Purpose | Example Modules |
|---|---|---|
ansible.builtin |
Core functionality | copy, template, command, user |
ansible.posix |
POSIX systems | authorized_key, synchronize |
community.general |
General utilities | interfaces_file, ini_file |
community.docker |
Docker management | docker_container, docker_image |
Installing Collections
# Install from requirements
cd ansible && uv run ansible-galaxy collection install -r requirements.yml
# Install specific collection
uv run ansible-galaxy collection install community.docker
Common Execution Patterns
Running Playbooks
# Basic execution
uv run ansible-playbook playbooks/my-playbook.yml
# With extra variables
uv run ansible-playbook playbooks/create-vm.yml \
-e "vm_name=docker-01" \
-e "vm_memory=4096"
# Limit to specific hosts
uv run ansible-playbook playbooks/update.yml --limit proxmox
# Check mode (dry run)
uv run ansible-playbook playbooks/deploy.yml --check --diff
# With tags
uv run ansible-playbook playbooks/setup.yml --tags "network,storage"
Linting
# Run ansible-lint
uv run ansible-lint ansible/playbooks/
Diagnostic Commands
# Check syntax without running
uv run ansible-playbook --syntax-check playbooks/my-playbook.yml
# List hosts in inventory
uv run ansible-inventory --list
# Test connection to all hosts
uv run ansible all -m ping
Task Naming Conventions
Use descriptive names with action verbs:
| Verb | Use When |
|---|---|
| Ensure | Verifying state exists |
| Configure | Modifying settings |
| Install | Adding packages |
| Create | Making new resources |
| Remove | Deleting resources |
| Deploy | Releasing applications |
| Update | Modifying existing resources |
Examples:
- name: Ensure Docker is installed
- name: Configure SSH security settings
- name: Create admin user account
- name: Deploy application configuration
Secrets Management
Secrets (passwords, API keys, tokens) live in plain YAML files on the local machine or in GitHub Actions secrets — no third-party secrets manager. Keep it simple.
# group_vars/all.yml or host_vars/hostname.yml (gitignored if sensitive)
db_password: 'changeme'
api_key: 'mykey'
Pass GitHub Actions secrets as extra vars at runtime:
uv run ansible-playbook playbooks/deploy.yml -e "db_password={{ secrets.DB_PASSWORD }}"
Always use no_log: true on tasks that reference these values to prevent exposure in logs.
Related Skills
- ansible-idempotency - Detailed changed_when/failed_when patterns
- ansible-error-handling - Block/rescue, retry patterns