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:bashuv run ansible-playbook playbooks/my-playbook.yml uv run ansible-lint uv run ansible-galaxy collection install -r requirements.yml
- •
Fully Qualified Collection Names (FQCN) - Avoid short module names:
yaml# CORRECT - name: Install package ansible.builtin.apt: name: nginx state: present # WRONG - deprecated short names - name: Install package apt: name: nginx - •
Control command/shell modules - Add
changed_whenandfailed_when:yaml- name: Check if service exists ansible.builtin.command: systemctl status myservice register: service_check changed_when: false failed_when: false
- •
Use
set -euo pipefail- In all shell scripts and shell module calls:yaml- name: Run pipeline command ansible.builtin.shell: | set -euo pipefail cat file.txt | grep pattern | wc -l args: executable: /bin/bash - •
Tag sensitive tasks - Use
no_log: truefor secrets:yaml- name: Set database password ansible.builtin.command: set-password {{ db_password }} no_log: true - •
Idempotency 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 (pvecm, pveceph, kubectl)
- •Running one-off scripts
Add proper controls:
- name: Create Proxmox API token
ansible.builtin.command: >
pveum user token add {{ username }}@pam {{ token_name }}
register: token_result
changed_when: "'already exists' not in token_result.stderr"
failed_when:
- token_result.rc != 0
- "'already exists' not in token_result.stderr"
no_log: true
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.proxmox | Proxmox VE | proxmox_vm, proxmox_kvm |
infisical.vault | Secrets management | read_secrets |
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.proxmox
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 mise run ansible-lint # Or directly uv run ansible-lint ansible/playbooks/
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
Variable Naming
Use snake_case with descriptive names:
# GOOD - clear, descriptive proxmox_api_user: terraform@pam docker_compose_version: "2.24.0" vm_memory_mb: 4096 # BAD - vague, abbreviated pve_usr: terraform@pam dc_ver: "2.24.0" mem: 4096
Quick Reference Commands
# Lint all Ansible files mise run ansible-lint # Run playbook with secrets from Infisical cd ansible && uv run ansible-playbook playbooks/my-playbook.yml # Check syntax uv run ansible-playbook --syntax-check playbooks/my-playbook.yml # List hosts in inventory uv run ansible-inventory --list # Test connection uv run ansible all -m ping
Common Anti-Patterns
Missing FQCN
# BAD
- name: Copy file
copy:
src: file.txt
dest: /tmp/
# GOOD
- name: Copy file
ansible.builtin.copy:
src: file.txt
dest: /tmp/
Uncontrolled Commands
# BAD - always shows changed, no error handling - name: Check status ansible.builtin.command: systemctl status app # GOOD - name: Check status ansible.builtin.command: systemctl status app register: status_check changed_when: false failed_when: false
Using shell When command Suffices
# BAD - shell not needed - name: List files ansible.builtin.shell: ls -la /tmp # GOOD - command is sufficient - name: List files ansible.builtin.command: ls -la /tmp changed_when: false
Missing no_log on Secrets
# BAD - password in logs
- name: Set password
ansible.builtin.command: set-password {{ password }}
# GOOD
- name: Set password
ansible.builtin.command: set-password {{ password }}
no_log: true
Related Skills
- •ansible-idempotency - Detailed changed_when/failed_when patterns
- •ansible-secrets - Infisical integration and security
- •ansible-proxmox - Proxmox-specific module selection
- •ansible-error-handling - Block/rescue, retry patterns