Skip to content

Ansible Quick Reference

All commands run from ~/ansible on the control node.


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 ops-01.internal
ansible-playbook playbooks/stacks.yml --limit ops-01.internal

# 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 ops-01.internal
ansible-playbook playbooks/stacks.yml --tags monitoring --limit ops-01.internal

# 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 ops-01.internal

# Dry run with file diffs shown
ansible-playbook playbooks/base.yml --check --diff --limit ops-01.internal

# Verbose output                          -v, -vv, -vvv for increasing detail
ansible-playbook playbooks/base.yml -v --limit ops-01.internal

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 ops-01.internal -a "docker ps"

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

# Use a specific module                   -m service uses the service module
ansible ops-01.internal -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 ops-01.internal -m debug -a "var=ansible_distribution_release"

Note

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


Vault (Secrets)

# Create encrypted file                   opens editor, encrypts on save
ansible-vault create inventory/group_vars/vault.yml

# Edit existing vault                     decrypts, opens editor, re-encrypts
ansible-vault edit inventory/group_vars/vault.yml

# View without editing                    decrypts to stdout
ansible-vault view inventory/group_vars/vault.yml

# Change vault password
ansible-vault rekey inventory/group_vars/vault.yml

Vault files need vars_files in the playbook to load:

vars_files:
  - ../inventory/group_vars/vault.yml

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
}

Checking Host Status

{
  echo '========== OPS-01 docker ps =========='
  ansible ops-01.internal -a "docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}'"

  echo
  echo '========== OPS-01 /opt/docker =========='
  ansible ops-01.internal -a "tree -L 2 /opt/docker"

  echo
  echo '========== OPS-01 services =========='
  ansible ops-01.internal -a "systemctl --no-pager --type=service --state=running"
}

Updating Docker Containers

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

# Update only ops-01
ansible-playbook playbooks/update.yml --limit ops-01.internal

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

# Manual update of a single stack on a host
ansible ops-01.internal -a "docker compose -f /opt/docker/monitoring/docker-compose.yml pull"
ansible ops-01.internal -a "docker compose -f /opt/docker/monitoring/docker-compose.yml up -d"

Docker Status

# All containers on a host
ansible ops-01.internal -a "docker ps -a --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'"

# Running containers only
ansible ops-01.internal -a "docker ps --format 'table {{.Names}}\t{{.Status}}'"

# Check a specific container's logs (last 50 lines)
ansible ops-01.internal -a "docker logs --tail 50 homeassistant"

# Follow logs live (run directly, not via Ansible)
ssh username@ops-01.internal "docker logs -f homeassistant"

# Restart a specific container
ansible ops-01.internal -a "docker restart homeassistant"

# Stop a specific container
ansible ops-01.internal -a "docker stop homeassistant"

# Start a specific container
ansible ops-01.internal -a "docker start homeassistant"

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

# Docker networks
ansible ops-01.internal -a "docker network ls"

# Docker volumes (check for orphans)
ansible ops-01.internal -a "docker volume ls"

# Prune unused images
ansible ops-01.internal -a "docker image prune -f"

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

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 ops-01.internal

# 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"

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 ops-01.internal -a "du -sh /mnt/appdata/* --max-depth=0"

# Check NAS backup repos
ansible ops-01.internal -a "ls -la /mnt/backups/"

# Docker disk usage
ansible ops-01.internal -a "docker system df"

Backups

# Run borgmatic backup manually on a host
ansible ops-01.internal -a "borgmatic --verbosity 1 --list --stats"

# Check latest backup archives
ansible ops-01.internal -a "borgmatic rlist --last 5"

# Check backup repo info
ansible ops-01.internal -a "borgmatic info"

# Verify backup integrity
ansible ops-01.internal -a "borgmatic check"

# Check backup cron is in place
ansible ops-01.internal -a "cat /etc/cron.d/borgmatic"

# See what's in the backup repo on the NAS
ansible ops-01.internal -a "ls -la /mnt/backups/ops-01/"

Service Management

# Check Docker daemon status
ansible ops-01.internal -m service -a "name=docker"

# Restart Docker daemon
ansible ops-01.internal -m service -a "name=docker state=restarted"

# Check if Tailscale is running
ansible ops-01.internal -m service -a "name=tailscaled"

# Start Tailscale if needed
ansible ops-01.internal -m service -a "name=tailscaled state=started"

# Check SSH daemon
ansible ops-01.internal -m service -a "name=sshd"

# Check code-server
ansible ops-01.internal -m service -a "name=code-server@username"

Network

# Check IP addresses
ansible ops-01.internal -a "ip -brief addr"

# Check listening ports
ansible ops-01.internal -a "ss -tlnp"

# Test DNS resolution
ansible ops-01.internal -a "dig prod-deb-01.internal +short"

# Check NAS mount is alive
ansible ops-01.internal -a "mountpoint /mnt/backups"

# Test connectivity to another host
ansible ops-01.internal -a "ping -c 2 nas.internal"

Vault (Secrets)

# Create encrypted file                   opens editor, encrypts on save
ansible-vault create inventory/group_vars/vault.yml

# Edit existing vault                     decrypts, opens editor, re-encrypts
ansible-vault edit inventory/group_vars/vault.yml

# View without editing                    decrypts to stdout
ansible-vault view inventory/group_vars/vault.yml

# Change vault password
ansible-vault rekey inventory/group_vars/vault.yml

Vault files need vars_files in the playbook to load:

vars_files:
  - ../inventory/group_vars/vault.yml

Troubleshooting

# Verbose playbook output
ansible-playbook playbooks/base.yml -v --limit ops-01.internal    # some detail
ansible-playbook playbooks/base.yml -vvv --limit ops-01.internal  # 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 ops-01.internal -m setup

# Show specific facts
ansible ops-01.internal -m setup -a "filter=ansible_distribution*"
ansible ops-01.internal -m setup -a "filter=ansible_memtotal_mb"

# Check a variable value
ansible ops-01.internal -m debug -a "var=docker_compose_dir"

# Test SSH manually
ssh -i ~/.ssh/ansible username@ops-01.internal "hostname"

# Check sudoers
ansible ops-01.internal -a "cat /etc/sudoers.d/username"

# Failed systemd units
ansible ops-01.internal -a "systemctl --failed"

# Journal for a service
ansible ops-01.internal -a "journalctl -u docker --no-pager -n 30"

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
  command: hostname
  register: result          # stores output in 'result'
- name: Use it
  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 '{{.Names}}: {{.Status}}'" 
ansible homelab -a "mountpoint /mnt/backups"
ansible homelab -a "systemctl --failed"

Inventory Targeting

# hosts.ini
[infra]           # group name
ops-01.internal   # host in group

[apps]
prod-deb-01.internal

[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 ops-01.internal -m ping    # single host
ansible all -m ping                # everything in inventory

Debugging

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

# List tasks a playbook would run
ansible-playbook playbooks/base.yml --list-tasks

# Show all facts about a host (OS, IP, RAM, etc.)
ansible ops-01.internal -m setup

# Show a specific fact
ansible ops-01.internal -m setup -a "filter=ansible_distribution*"

# Test a playbook without running it
ansible-playbook playbooks/base.yml --check --diff --limit ops-01.internal
#                                    ^dry run  ^show file diffs