Skip to content

Ansible Quick Reference

All commands run from ~/ansible on the control node. This page is the command cheat-sheet; the explanations are in Ad-hoc commands & running playbooks, and fixes for common errors are in Troubleshooting.


Deploying and Updating

# Deploy everything to all hosts
ansible-playbook playbooks/site.yml

# Deploy base config only (packages, SSH, Docker, borgmatic, etc.)
ansible-playbook playbooks/base.yml

# Deploy stacks only (compose services)
ansible-playbook playbooks/stacks.yml

# Target a single host - --limit filters which hosts
ansible-playbook playbooks/base.yml --limit server1.example.com
ansible-playbook playbooks/stacks.yml --limit server1.example.com

# Target a group - groups are defined in hosts.ini
ansible-playbook playbooks/base.yml --limit infra
ansible-playbook playbooks/stacks.yml --limit apps

# Run a specific role only
ansible-playbook playbooks/base.yml --tags docker --limit server1.example.com
ansible-playbook playbooks/stacks.yml --tags monitoring --limit server1.example.com

# Run everything except one role  - --skip-tags excludes a tag
ansible-playbook playbooks/base.yml --skip-tags borgmatic

# Dry run (preview changes without applying) - --check simulates, changes nothing
ansible-playbook playbooks/base.yml --check --limit server1.example.com

# Dry run with file diffs shown
ansible-playbook playbooks/base.yml --check --diff --limit server1.example.com

# Verbose output                          -v, -vv, -vvv for increasing detail
ansible-playbook playbooks/base.yml -v --limit server1.example.com

Ad-Hoc Commands

Run a single command without a playbook. Format: ansible <target> -m <module> -a "<args>"

# Ping (test connectivity + sudo)         -m ping uses the ping module
ansible homelab -m ping

# Run a shell command                     -a uses the command module by default
ansible server1.example.com -a "docker ps"

# Run against all hosts
ansible homelab -a "uptime"

# Use a specific module                   -m service uses the service module
ansible server1.example.com -m service -a "name=docker state=restarted"

# Copy a file                             -m copy uses the copy module
ansible homelab -m copy -a "src=/tmp/file.txt dest=/tmp/file.txt"

# Check a variable value                  -m debug prints variables
ansible server1.example.com -m debug -a "var=docker_compose_dir"

# Install a package
ansible server1.example.com -m apt -a "name=tree state=present" --become

Note

-a (command module) always reports CHANGED even for read-only commands. This is normal.


Vault (Secrets)

The vault commands (create, edit, view, rekey, encrypt_string) and how the vault file is loaded with vars_files are on the Vault page.


Display Ansible Files

cd ~/ansible && {
  printf '\n========== TREE ==========\n'
  tree -L 4

  while IFS= read -r f; do
    [ -f "$f" ] || continue
    printf '\n========== %s ==========\n' "$f"
    sed -n '1,250p' "$f"
  done <<'EOF'
ansible.cfg
inventory/hosts.ini
inventory/group_vars/all.yml
playbooks/base.yml
playbooks/stacks.yml
playbooks/update.yml
roles/base/tasks/main.yml
roles/base/handlers/main.yml
roles/docker/tasks/main.yml
roles/docker/handlers/main.yml
roles/mounts/tasks/main.yml
roles/borgmatic/tasks/main.yml
roles/codeserver/tasks/main.yml
roles/stacks_infra/tasks/main.yml
roles/stacks_apps/tasks/main.yml
roles/tailscale/tasks/main.yml
roles/update_containers/tasks/main.yml
EOF

  printf '\n========== HOST_VARS ==========\n'
  find inventory/host_vars -maxdepth 2 -type f \( -name '*.yml' -o -name '*.yaml' \) | sort | while IFS= read -r f; do
    printf '\n========== %s ==========\n' "$f"
    sed -n '1,250p' "$f"
  done
}

Updating Docker Containers

# Pull latest images and recreate all containers on all hosts
ansible-playbook playbooks/update.yml

# Update only server1
ansible-playbook playbooks/update.yml --limit server1.example.com

# Update only nas
ansible-playbook playbooks/update.yml --limit nas.example.com

# Manual update of a single stack on a host
ansible server1.example.com -a "docker compose -f /opt/docker/monitoring/docker-compose.yml pull"
ansible server1.example.com -a "docker compose -f /opt/docker/monitoring/docker-compose.yml up -d"

Docker Status

# All containers on a host
ansible server1.example.com -a "docker ps -a --format '{% raw %}table {{.Names}}\t{{.Status}}\t{{.Ports}}{% endraw %}'"

# Running containers only
ansible server1.example.com -a "docker ps --format '{% raw %}table {{.Names}}\t{{.Status}}{% endraw %}'"

# Check a specific container's logs (last 50 lines)
ansible server1.example.com -a "docker logs --tail 50 homeassistant"

# Follow logs live (run directly, not via Ansible)
ssh username@server1.example.com "docker logs -f homeassistant"

# Restart a specific container
ansible server1.example.com -a "docker restart homeassistant"

# Stop a specific container
ansible server1.example.com -a "docker stop homeassistant"

# Start a specific container
ansible server1.example.com -a "docker start homeassistant"

# Check all containers across all hosts
ansible homelab -a "docker ps --format '{% raw %}table {{.Names}}\t{{.Status}}{% endraw %}'"

# Docker networks
ansible server1.example.com -a "docker network ls"

# Docker volumes (check for orphans)
ansible server1.example.com -a "docker volume ls"

# Prune unused images
ansible server1.example.com -a "docker image prune -f"

# Prune everything (images, containers, networks, volumes)
# CAREFUL: removes all stopped containers and unused volumes
ansible server1.example.com -a "docker system prune --volumes -f"

Docker --format strings need {% raw %}

Ansible runs -a arguments through Jinja2, so a bare {{.Names}} fails with "Syntax error in template". Wrapping the format string in {% raw %}…{% endraw %} passes it to Docker untouched, as in the commands above.


Editing a Stack

Workflow: edit template on Control Node, push with Ansible.

# 1. Edit the compose template
micro ~/ansible/roles/stacks_infra/templates/monitoring-compose.yml.j2

# 2. Push the change
ansible-playbook playbooks/stacks.yml --limit server1.example.com

# Never edit compose files directly on the hosts.
# Ansible overwrites them on next run.

Checking Host Status

# Ping all hosts (tests SSH + sudo)
ansible homelab -m ping

# Uptime
ansible homelab -a "uptime"

# OS and kernel version
ansible homelab -a "uname -r"
ansible homelab -a "cat /etc/os-release"

# Who is logged in
ansible homelab -a "who"

# System load
ansible homelab -a "cat /proc/loadavg"

# Memory usage
ansible homelab -a "free -h"

Everything about one host at once (containers, compose dirs, running services):

{
  echo '========== SERVER1 docker ps =========='
  ansible server1.example.com -a "docker ps --format '{% raw %}table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}{% endraw %}'"

  echo
  echo '========== SERVER1 /opt/docker =========='
  ansible server1.example.com -a "tree -L 2 /opt/docker"

  echo
  echo '========== SERVER1 services =========='
  ansible server1.example.com -a "systemctl --no-pager --type=service --state=running"
}

Checking Disk

# Disk usage on all hosts
ansible homelab -a "df -h"

# Appdata disk specifically
ansible homelab -a "df -h /mnt/appdata"

# NAS backup mount
ansible homelab -a "df -h /mnt/backups"

# Largest directories in appdata
ansible server1.example.com -m shell -a "du -sh /mnt/appdata/*"   # shell module: the default command module does not expand *

# Check NAS backup repos
ansible server1.example.com -a "ls -la /mnt/backups/"

# Docker disk usage
ansible server1.example.com -a "docker system df"

Backups

# Run borgmatic backup manually on a host
ansible server1.example.com -a "borgmatic --verbosity 1 --list --stats"

# Check latest backup archives
ansible server1.example.com -a "borgmatic rlist --last 5"

# Check backup repo info
ansible server1.example.com -a "borgmatic info"

# Verify backup integrity
ansible server1.example.com -a "borgmatic check"

# Check backup cron is in place
ansible server1.example.com -a "cat /etc/cron.d/borgmatic"

# See what's in the backup repo on the NAS
ansible server1.example.com -a "ls -la /mnt/backups/server1/"

Service Management

# Check Docker daemon status
ansible server1.example.com -m service -a "name=docker"

# Restart Docker daemon
ansible server1.example.com -m service -a "name=docker state=restarted"

# Check if Tailscale is running
ansible server1.example.com -m service -a "name=tailscaled"

# Start Tailscale if needed
ansible server1.example.com -m service -a "name=tailscaled state=started"

# Check SSH daemon
ansible server1.example.com -m service -a "name=sshd"

# Check code-server
ansible server1.example.com -m service -a "name=code-server@username"

Network

# Check IP addresses
ansible server1.example.com -a "ip -brief addr"

# Check listening ports
ansible server1.example.com -a "ss -tlnp"

# Test DNS resolution
ansible server1.example.com -a "dig server2.example.com +short"

# Check NAS mount is alive
ansible server1.example.com -a "mountpoint /mnt/backups"

# Test connectivity to another host
ansible server1.example.com -a "ping -c 2 nas.example.com"

Troubleshooting & Debugging

# Verbose playbook output
ansible-playbook playbooks/base.yml -v --limit server1.example.com    # some detail
ansible-playbook playbooks/base.yml -vvv --limit server1.example.com  # SSH debug

# List what hosts a playbook would target
ansible-playbook playbooks/stacks.yml --list-hosts

# List what tasks would run
ansible-playbook playbooks/stacks.yml --list-tasks

# Show all Ansible facts for a host (OS, IP, RAM, disks, etc.)
ansible server1.example.com -m setup

# Show specific facts
ansible server1.example.com -m setup -a "filter=ansible_distribution*"
ansible server1.example.com -m setup -a "filter=ansible_memtotal_mb"

# Check a variable value
ansible server1.example.com -m debug -a "var=docker_compose_dir"

# Test SSH manually
ssh -i ~/.ssh/ansible username@server1.example.com "hostname"

# Check sudoers
ansible server1.example.com -a "cat /etc/sudoers.d/username"

# Failed systemd units
ansible server1.example.com -a "systemctl --failed"

# Journal for a service
ansible server1.example.com -a "journalctl -u docker --no-pager -n 30"

# Test a playbook without running it
ansible-playbook playbooks/base.yml --check --diff --limit server1.example.com
#                                    ^dry run  ^show file diffs

Step-by-step fixes for SSH, sudo, DNS and template errors: Troubleshooting.


Key Concepts

Idempotent -- Running twice gives the same result. Second run = changed=0. If a task always shows changed, add creates: or when: to fix it.

Roles -- Reusable bundles. Each role has tasks/main.yml (what to do), templates/ (config files with variables), handlers/main.yml (actions triggered by changes).

Handlers -- Only run when notified. If a template changes, the handler fires (e.g., restart sshd). If nothing changes, the handler is skipped.

Tags -- Labels on roles. Defined in the playbook: { role: docker, tags: ['docker'] }. Filter with --tags or --skip-tags.

Templates -- Files ending in .j2. Ansible replaces {{ variable }} with values from group_vars/host_vars. Loops use {% for item in list %}.

Register -- Captures task output into a variable for later use:

- name: Get something
  ansible.builtin.command: hostname
  register: result          # stores output in 'result'
- name: Use it
  ansible.builtin.debug:
    msg: "{{ result.stdout }}"  # access with .stdout

Quick Health Check (All Hosts)

Run these in sequence for a full overview:

ansible homelab -m ping
ansible homelab -a "uptime"
ansible homelab -a "free -h"
ansible homelab -a "df -h /mnt/appdata"
ansible homelab -a "docker ps --format '{% raw %}{{.Names}}: {{.Status}}{% endraw %}'" 
ansible homelab -a "mountpoint /mnt/backups"
ansible homelab -a "systemctl --failed"

Inventory Targeting

# hosts.ini
[infra]           # group name
server1.example.com   # host in group

[apps]
server2.example.com

[homelab:children] # parent group containing other groups
infra
apps
ansible infra -m ping              # all hosts in infra group
ansible homelab -m ping            # all hosts in homelab (infra + apps)
ansible server1.example.com -m ping    # single host
ansible all -m ping                # everything in inventory

Useful Flags and Options

A reference for common command-line flags.

Flag Purpose Example
--limit Restrict to specific host(s) or group(s) --limit server3.example.com
--check Dry run, show what would change --check
--diff Show file-level diffs for template changes --diff
--tags Run only tasks/roles with these tags --tags docker,base
--skip-tags Run everything except these tags --skip-tags borgmatic
-v Verbose output (add more v's for more detail) -vvv for SSH debug
--list-hosts Show which hosts a playbook would target --list-hosts
--list-tasks Show which tasks would run --list-tasks
--list-tags Show which tags exist in a playbook --list-tags
--become Use sudo (already default in our cfg, but needed for ad-hoc) --become

Combining flags:

# Dry run with diffs against one host, verbose
ansible-playbook playbooks/base.yml --check --diff --limit server3.example.com -v

Ref: https://docs.ansible.com/projects/ansible/latest/cli/ansible-playbook.html