Ansible

Configuration management and automation with Ansible - ad-hoc commands, playbooks, roles, vault, and common modules.

Ad-Hoc Commands

Connectivity test
ansible all -i inventory -m ping
Run shell command on a group
ansible webservers -m shell -a 'uptime'
web-01 | CHANGED | rc=0 >>
 14:32:01 up 42 days,  3:15,  1 user,  load average: 0.08, 0.03, 0.01
Gather specific facts
ansible all -m setup -a 'filter=ansible_os_family'
Copy file to all hosts
ansible all -m copy -a 'src=resolv.conf dest=/etc/resolv.conf' --become
Install package (idempotent)
ansible all -m package -a 'name=htop state=present' --become
Restart a service
ansible all -m systemd -a 'name=sshd state=restarted' --become

PATTERN: Ad-hoc for one-off tasks. Playbooks for anything you will run twice.

Playbook Execution

Basic run
ansible-playbook site.yml
Dry run with diffs (safest preview)
ansible-playbook site.yml --check --diff
Limit to specific hosts or groups
ansible-playbook site.yml --limit webservers
ansible-playbook site.yml --limit 'web-01,web-02'
Run only tagged tasks
ansible-playbook site.yml --tags deploy
ansible-playbook site.yml --skip-tags slow
Pass extra variables
ansible-playbook site.yml -e "env=prod version=2.1"
ansible-playbook site.yml -e @vars.yml
Verbose debugging
ansible-playbook site.yml -v       # Minimal
ansible-playbook site.yml -vvv     # Connection debug
ansible-playbook site.yml -vvvv    # Full wire-level
List tasks and hosts without executing
ansible-playbook site.yml --list-tasks
ansible-playbook site.yml --list-hosts
Resume from specific task
ansible-playbook site.yml --start-at-task="Deploy app"

MUSCLE MEMORY: --check --diff before every real run.

Playbook Structure

Minimal playbook
---
- name: Configure web servers
  hosts: webservers
  become: true

  tasks:
    - name: Install nginx
      ansible.builtin.package:
        name: nginx
        state: present

    - name: Start and enable nginx
      ansible.builtin.systemd:
        name: nginx
        state: started
        enabled: true

    - name: Deploy config
      ansible.builtin.template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
        owner: root
        group: root
        mode: '0644'
      notify: Reload nginx

  handlers:
    - name: Reload nginx
      ansible.builtin.systemd:
        name: nginx
        state: reloaded
Multi-play playbook
---
- name: Common setup
  hosts: all
  become: true
  roles:
    - common
    - security-baseline

- name: Web tier
  hosts: webservers
  become: true
  roles:
    - nginx
    - certbot

STRUCTURE: Handlers only fire when notified by a changed task, and run once at end of play.

Inventory

INI format inventory
[webservers]
web-01 ansible_host=10.50.1.101
web-02 ansible_host=10.50.1.102

[dbservers]
db-01 ansible_host=10.50.1.110

[all:vars]
ansible_user=ansible
ansible_ssh_private_key_file=~/.ssh/id_ed25519

[prod:children]
webservers
dbservers
YAML format inventory
all:
  children:
    webservers:
      hosts:
        web-01:
          ansible_host: 10.50.1.101
        web-02:
          ansible_host: 10.50.1.102
    dbservers:
      hosts:
        db-01:
          ansible_host: 10.50.1.110
  vars:
    ansible_user: ansible
Inspect inventory
# Tree view
ansible-inventory --graph

# JSON export with jq
ansible-inventory --list | jq '.webservers.hosts'

GOTCHA: YAML inventory is cleaner for complex grouping. INI is fine for flat lists.

Roles

Scaffold a role
ansible-galaxy init roles/nginx
roles/nginx/
├── defaults/main.yml     # Default variables (lowest precedence)
├── files/                # Static files for copy module
├── handlers/main.yml     # Handler definitions
├── meta/main.yml         # Role metadata, dependencies
├── tasks/main.yml        # Task entry point
├── templates/            # Jinja2 templates
└── vars/main.yml         # Variables (high precedence)
Use role in playbook
- name: Deploy stack
  hosts: webservers
  become: true
  roles:
    - common
    - { role: nginx, nginx_port: 8080 }
    - role: certbot
      when: env == 'prod'
Install roles from requirements
# requirements.yml
ansible-galaxy install -r requirements.yml
# requirements.yml
roles:
  - name: geerlingguy.docker
    version: "6.1.0"

collections:
  - name: community.general
    version: ">=7.0.0"

Ansible Vault

Create encrypted file
ansible-vault create secrets.yml
Encrypt existing file
ansible-vault encrypt vars.yml
Edit encrypted file
ansible-vault edit secrets.yml
Encrypt single variable for inline use
ansible-vault encrypt_string 'supersecret' --name 'db_password'
db_password: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          61626364656667...
Run playbook with vault
# Prompt for password
ansible-playbook site.yml --ask-vault-pass

# Password file (CI/CD)
ansible-playbook site.yml --vault-password-file ~/.vault_pass

SECURITY: Never commit --vault-password-file targets. Use ANSIBLE_VAULT_PASSWORD_FILE env var in CI.

Common Modules

File operations
- name: Create directory
  ansible.builtin.file:
    path: /opt/app
    state: directory
    owner: app
    mode: '0755'

- name: Template config
  ansible.builtin.template:
    src: app.conf.j2
    dest: /etc/app/app.conf
    backup: true
  notify: Restart app

- name: Download file
  ansible.builtin.get_url:
    url: https://example.com/binary
    dest: /usr/local/bin/binary
    mode: '0755'
    checksum: sha256:abc123...
User management
- name: Create service user
  ansible.builtin.user:
    name: app
    system: true
    shell: /sbin/nologin
    create_home: false

- name: Add SSH key
  ansible.posix.authorized_key:
    user: ansible
    key: "{{ lookup('file', '~/.ssh/id_ed25519.pub') }}"
Package and service
- name: Install packages
  ansible.builtin.package:
    name:
      - nginx
      - certbot
      - python3-certbot-nginx
    state: present

- name: Enable and start service
  ansible.builtin.systemd:
    name: nginx
    state: started
    enabled: true
    daemon_reload: true
Command execution
- name: Run only if file missing
  ansible.builtin.command:
    cmd: /opt/app/setup.sh
    creates: /opt/app/.installed

- name: Shell with pipe
  ansible.builtin.shell:
    cmd: journalctl -u app --no-pager | tail -20
  register: app_logs
  changed_when: false

Conditionals & Loops

When conditional
- name: Install on Debian family
  ansible.builtin.apt:
    name: nginx
    state: present
  when: ansible_os_family == 'Debian'

- name: Install on RedHat family
  ansible.builtin.dnf:
    name: nginx
    state: present
  when: ansible_os_family == 'RedHat'
Loop over list
- name: Create users
  ansible.builtin.user:
    name: "{{ item }}"
    state: present
  loop:
    - deploy
    - monitor
    - backup
Loop over dict
- name: Create directories with specific owners
  ansible.builtin.file:
    path: "{{ item.path }}"
    owner: "{{ item.owner }}"
    state: directory
  loop:
    - { path: /opt/app, owner: app }
    - { path: /var/log/app, owner: app }
    - { path: /etc/app, owner: root }
Register and check result
- name: Check if config exists
  ansible.builtin.stat:
    path: /etc/app/app.conf
  register: config_file

- name: Generate config if missing
  ansible.builtin.template:
    src: app.conf.j2
    dest: /etc/app/app.conf
  when: not config_file.stat.exists

Debugging

Debug module
- name: Print variable
  ansible.builtin.debug:
    var: ansible_default_ipv4.address

- name: Print message
  ansible.builtin.debug:
    msg: "Deploying version {{ app_version }} to {{ inventory_hostname }}"
Step through playbook interactively
ansible-playbook site.yml --step
Run with callback for timing
ANSIBLE_CALLBACKS_ENABLED=timer,profile_tasks ansible-playbook site.yml

PATTERN: register + debug to inspect module output during development.

Quick Reference

Task Command

Ping all hosts

ansible all -i inventory -m ping

Ad-hoc shell command

ansible webservers -m shell -a 'uptime'

Gather specific fact

ansible all -m setup -a 'filter=ansible_os_family'

Run playbook

ansible-playbook site.yml

Dry run with diffs

ansible-playbook site.yml --check --diff

Limit to group

ansible-playbook site.yml --limit webservers

Run tagged tasks only

ansible-playbook site.yml --tags deploy

Extra variables

ansible-playbook site.yml -e "env=prod"

Vault create

ansible-vault create secrets.yml

Vault encrypt string

ansible-vault encrypt_string 'val' --name 'key'

Scaffold role

ansible-galaxy init my_role

Install requirements

ansible-galaxy install -r requirements.yml

Inventory graph

ansible-inventory --graph

List tasks

ansible-playbook site.yml --list-tasks

Verbose debug

ansible-playbook site.yml -vvv