# Ansible Cheatsheet

- URL: https://karandeepsingh.ca/cheatsheets/devops/ansible/
- Author: Karandeep Singh
- Published: 2025-07-19
- Tags: automation, linux
- Summary: Quick-reference Ansible guide. Inventories, ad-hoc commands, playbooks, module table, loops and conditionals, roles layout, Vault, and Jinja2 templating.

Ansible is what I grab the moment a server count goes above one. These notes are the stuff I actually type: inventory patterns, the module table I scan for every task, and the Vault commands I keep mixing up. I usually run these playbooks from a [Jenkins](https://karandeepsingh.ca/cheatsheets/devops/jenkins/) stage against hosts provisioned by [CloudFormation](https://karandeepsingh.ca/cheatsheets/devops/cloudformation/).

## Inventory

> **Docs:** [How to build your inventory, Ansible docs](https://docs.ansible.com/ansible/latest/inventory_guide/intro_inventory.html)

```yaml
# inventory.ini (INI format)
[web]
web1.example.com
web2.example.com ansible_host=10.0.1.12

[db]
db1.example.com ansible_user=postgres

[prod:children]
web
db

[prod:vars]
ansible_user=deploy
env_name=prod
```

```yaml
# inventory.yaml (YAML format)
all:
  children:
    web:
      hosts:
        web1.example.com:
        web2.example.com: { ansible_host: 10.0.1.12 }
    db:
      hosts:
        db1.example.com:
      vars: { ansible_user: postgres }
```

Per-host/per-group variable files auto-load from `group_vars/all.yaml`, `group_vars/<group>.yaml` (or a `group_vars/<group>/` directory, vault files included), and `host_vars/<host>.yaml` next to the inventory or playbook.

```bash
ansible-inventory -i inventory.yaml --graph     # visualise groups
```

## Ad-hoc Commands

> **Docs:** [Introduction to ad hoc commands, Ansible docs](https://docs.ansible.com/ansible/latest/command_guide/intro_adhoc.html)

```bash
ansible all  -i inventory.yaml -m ping                       # connectivity check
ansible web  -a 'uptime'                                     # default module: command
ansible db   -b -m service -a 'name=postgresql state=restarted'   # -b = become (sudo)
ansible all  -b -m apt -a 'name=htop state=present update_cache=yes'
ansible 'web1*' -m shell -a 'ss -tlnp | grep 443'            # shell when you need pipes
ansible all  --limit 'prod:!db' -m ping                      # pattern: prod except db
```

Useful flags: `-f 20` (forks), `-K` (ask sudo password), `-u deploy` (remote user), `--one-line`.

## Playbook Anatomy

> **Docs:** [Ansible playbooks, Ansible docs](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_intro.html)

```yaml
---
- name: Configure web tier
  hosts: web
  become: true
  vars_files:
    - vars/common.yaml
  tasks:
    - name: Install nginx
      ansible.builtin.apt:
        name: nginx
        state: present
        update_cache: true
      tags: [packages]

    - name: Deploy config
      ansible.builtin.template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
      notify: Restart nginx
      tags: [config]

  handlers:
    - name: Restart nginx
      ansible.builtin.service:
        name: nginx
        state: restarted
```

```bash
ansible-playbook -i inventory.yaml site.yaml
ansible-playbook site.yaml --limit web --tags config --check --diff
ansible-playbook site.yaml --syntax-check --list-tasks --list-hosts
```

## Common Modules

> **Docs:** [ansible.builtin collection index, Ansible docs](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/index.html)

| Module | Purpose | Key params |
|---|---|---|
| `apt` / `yum` / `dnf` | Package management (`package` = distro-agnostic) | `name`, `state`, `update_cache` |
| `copy` | Push static file/content | `src`, `dest`, `mode`, `content` |
| `template` | Render Jinja2 to remote | `src` (.j2), `dest`, `validate` |
| `file` | Files, dirs, links, perms | `path`, `state` (touch/directory/link/absent), `mode`, `owner` |
| `service` / `systemd` | Manage services | `name`, `state`, `enabled`, `daemon_reload` |
| `user` | Manage users | `name`, `groups`, `shell`, `state` |
| `lineinfile` | Edit single line idempotently | `path`, `regexp`, `line`, `state` |
| `git` | Clone/update repos | `repo`, `dest`, `version` |
| `uri` | HTTP calls / health checks | `url`, `method`, `status_code`, `body_format` |
| `command` | Run command (no shell) | `cmd`, `creates`, `chdir` |
| `shell` | Run through /bin/sh | needed for pipes, redirects, `$VAR` |

`command` vs `shell`: prefer `command` (safer, no shell injection); use `shell` only when you need pipes/globs/env expansion. Neither is idempotent by default; guard with `creates:`, `changed_when:`, or better, find a real module.

## Variables & Precedence

> **Docs:** [Using variables, Ansible docs](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_variables.html)

Low → high (abridged to the ones that matter day-to-day): role `defaults/main.yaml` → `group_vars/all` → `group_vars/<group>` → `host_vars/<host>` → play `vars:`/`vars_files:` → task `vars:` → `set_fact`/registered vars → extra vars `-e key=value` (always wins).

```yaml
- name: Register output for later
  ansible.builtin.command: git rev-parse --short HEAD
  register: git_sha
  changed_when: false
# use later as: {{ git_sha.stdout }}
```

## Facts

> **Docs:** [Discovering variables: facts and magic variables, Ansible docs](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_vars_facts.html)

```bash
ansible web1 -m setup                              # everything
ansible web1 -m setup -a 'filter=ansible_mem*'     # filtered
```

Common ones: `ansible_hostname`, `ansible_distribution`, `ansible_default_ipv4.address`, `ansible_processor_vcpus`, `ansible_memtotal_mb`. Set `gather_facts: false` on plays that don't need them; big speedup on large inventories.

## Loops & Conditionals

> **Docs:** [Loops, Ansible docs](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_loops.html)

```yaml
- name: Install packages
  ansible.builtin.apt:
    name: "{{ item }}"
    state: present
  loop: [nginx, certbot, jq]

- name: Create users with attributes
  ansible.builtin.user:
    name: "{{ item.name }}"
    groups: "{{ item.groups }}"
  loop:
    - { name: alice, groups: sudo }
    - { name: bob,   groups: docker }

- name: Only on Debian prod hosts   # list items are AND-ed
  ansible.builtin.apt:
    name: unattended-upgrades
  when:
    - ansible_os_family == 'Debian'
    - env_name == 'prod'
```

Retry pattern: `until: result.status == 200` with `retries: 10` and `delay: 5` on a registered task.

## Handlers & notify

> **Docs:** [Handlers: running operations on change, Ansible docs](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_handlers.html)

Handlers run once at the end of the play, only if something notified them, in the order they are defined (not notification order).

```yaml
tasks:
  - name: Update nginx config
    ansible.builtin.template:
      src: nginx.conf.j2
      dest: /etc/nginx/nginx.conf
    notify: Reload nginx

handlers:
  - name: Reload nginx
    ansible.builtin.service:
      name: nginx
      state: reloaded
```

Force handlers to fire mid-play with a `ansible.builtin.meta: flush_handlers` task.

## Roles Layout

> **Docs:** [Roles, Ansible docs](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_reuse_roles.html)

```bash
roles/nginx/
├── defaults/main.yaml     # overridable defaults (lowest precedence)
├── vars/main.yaml         # role constants (high precedence)
├── tasks/main.yaml        # entry point
├── handlers/main.yaml
├── templates/ + files/    # nginx.conf.j2, static assets
└── meta/main.yaml         # dependencies, galaxy info
```

```bash
ansible-galaxy init roles/myrole              # scaffold the layout above
ansible-galaxy install -r requirements.yaml   # pull external roles/collections
```

Apply in a play with `roles: [common, nginx]`, or `role: nginx` plus inline `vars:` to override defaults.

## Vault

> **Docs:** [Protecting sensitive data with Ansible Vault, Ansible docs](https://docs.ansible.com/ansible/latest/vault_guide/index.html)

```bash
ansible-vault create  group_vars/prod/vault.yaml     # new encrypted file
ansible-vault edit    group_vars/prod/vault.yaml
ansible-vault encrypt secrets.yaml
ansible-vault decrypt secrets.yaml
ansible-vault encrypt_string 's3cret' --name db_password   # inline single value

ansible-playbook site.yaml --ask-vault-pass
ansible-playbook site.yaml --vault-password-file ~/.vault_pass   # chmod 600, gitignore it
```

Convention: keep secrets in `vault.yaml` prefixed `vault_`, reference them from a plaintext `vars.yaml` (`db_password: "{{ vault_db_password }}"`) so `grep` still finds variable names.

## Tags, Check Mode & Diff

**tip:**
`--check` is only as honest as your modules. A task using `command` or
`shell` is skipped in check mode rather than simulated, so a playbook can
report "no changes" and still change things on a real run. Pair the shell
task with `check_mode: false` and a `changed_when` you have thought about.

```diff
# roles/api/tasks/main.yml
@@ -3,6 +3,10 @@
 - name: Reload the service config
-  shell: systemctl reload myapi
+  ansible.builtin.systemd_service:
+    name: myapi
+    state: reloaded
+  # a real module reports changed/ok correctly and is simulated by --check,
+  # which `shell` never is
```

> **Docs:** [Tags, Ansible docs](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_tags.html)

```bash
ansible-playbook site.yaml --tags 'config,certs'
ansible-playbook site.yaml --skip-tags packages
ansible-playbook site.yaml --check --diff        # dry run + show file changes
```

`--check` predicts changes without applying; `--diff` shows template/lineinfile deltas. Tasks that must always/never run: `tags: always` / `tags: never`.

**tip:**
Make `--check --diff` against prod part of your review flow. A playbook that errors in check mode (usually a `command` task whose output later tasks depend on) is telling you where idempotency is broken. Fix it with `check_mode: false` on the task or a proper module.

## ansible.cfg Essentials

> **Docs:** [Ansible configuration settings, Ansible docs](https://docs.ansible.com/ansible/latest/reference_appendices/config.html)

```yaml
# ansible.cfg (project root wins over ~/.ansible.cfg)
[defaults]
inventory = inventory/inventory.yaml
remote_user = deploy
forks = 20
host_key_checking = False        # convenient for cattle; understand the tradeoff
stdout_callback = yaml

[privilege_escalation]
become = True

[ssh_connection]
pipelining = True                # big speedup
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
```

## Jinja2 Templating Basics

> **Docs:** [Templating (Jinja2), Ansible docs](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_templating.html)

```yaml
# templates/nginx.conf.j2
worker_processes {{ ansible_processor_vcpus }};
events { worker_connections {{ nginx_worker_connections | default(1024) }}; }
{% for site in sites %}
server {
  listen 80;
  server_name {{ site.domain }};
{% if site.redirect_https | default(false) %}
  return 301 https://{{ site.domain }}$request_uri;
{% endif %}
}
{% endfor %}
```

Filters I use constantly: `default('x')`, `join(',')`, `to_yaml` / `to_nice_json`, `regex_replace('a', 'b')`, `b64encode`, `ternary('yes', 'no')`. Test any expression fast with `ansible localhost -m debug -a "msg={{ expr }}"`.

**Question:** Run your main playbook with `--check --diff` against production. How many tasks report a change you did not expect — and how many are silently skipped because they use `shell`?

## Related Cheatsheets

- [AWS CloudFormation](https://karandeepsingh.ca/cheatsheets/devops/cloudformation/): intrinsic functions, change sets, and drift detection to provision the infrastructure these playbooks configure
- [Jenkins](https://karandeepsingh.ca/cheatsheets/devops/jenkins/): wire `ansible-playbook` into declarative pipelines with credential binding, parameters, and parallel stages
- [Bash Basics](https://karandeepsingh.ca/cheatsheets/bash/bash-basics/): quoting, exit codes, and error handling for every `command`/`shell` task and dynamic-inventory script

Everything else is in the [DevOps Tooling group](https://karandeepsingh.ca/cheatsheets/devops/), or start from the full [cheatsheet library](https://karandeepsingh.ca/cheatsheets/).
