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