Ad-hoc Commands & Running Safely
Two things build confidence with Ansible: running quick ad-hoc commands to inspect hosts, and previewing changes before you apply them. This page covers both. For the full list of ready-made commands for this homelab, see the Quick Reference.
Ad-hoc commands
An ad-hoc command runs a single module against hosts without writing a playbook. Format:
ansible <target> -m <module> -a "<arguments>"
# Connectivity + sudo check (the 'ping' module — not ICMP)
ansible homelab -m ping
# Run a shell command (command module is the default, so -m is optional)
ansible ops-01.internal -a "docker ps"
ansible homelab -a "uptime"
# Use a specific module
ansible ops-01.internal -m service -a "name=docker state=restarted"
ansible ops-01.internal -m ansible.builtin.apt -a "name=htop state=present"
# Print a variable or fact
ansible ops-01.internal -m debug -a "var=ansible_distribution_release"
<target> is any inventory pattern: a host
(ops-01.internal), a group (infra, homelab), or all.
-a commands always say CHANGED
The command/shell modules can't tell whether they changed anything, so
ad-hoc shell commands report changed even when read-only. That's expected —
see Writing Tasks → changed_when.
Check mode — the dry run
--check simulates a play: Ansible reports what would change but writes
nothing. This is the single most useful habit for running with confidence.
# Preview the whole base playbook against one host
ansible-playbook playbooks/base.yml --check --limit ops-01.internal
Add --diff to see the exact before/after of every file a template or
copy task would write:
ansible-playbook playbooks/base.yml --check --diff --limit ops-01.internal
Check mode isn't perfect
Some tasks can't be simulated — e.g. a command that a later task depends on.
Those may show as skipped or error under --check. It's a preview, not a
guarantee, but it catches the vast majority of surprises.
Limiting scope — --limit
Never test on everything at once. Target one host first, then widen.
ansible-playbook playbooks/base.yml --limit ops-01.internal # one host
ansible-playbook playbooks/base.yml --limit infra # one group
ansible-playbook playbooks/base.yml --limit '!nas.internal' # everything except nas
Tags — run part of a play
Tag your roles/tasks, then run or skip subsets:
# Only the docker role
ansible-playbook playbooks/base.yml --tags docker --limit ops-01.internal
# Everything except borgmatic
ansible-playbook playbooks/base.yml --skip-tags borgmatic
# See which tags exist in a playbook
ansible-playbook playbooks/base.yml --list-tags
Look before you leap
# Which hosts would this run against?
ansible-playbook playbooks/stacks.yml --list-hosts
# Which tasks would run, in order?
ansible-playbook playbooks/base.yml --list-tasks
# Verbose output: -v (some), -vv (more), -vvv (SSH-level debug)
ansible-playbook playbooks/base.yml -v --limit ops-01.internal
A safe workflow
graph LR
A["--list-tasks"] --> B["--check --diff --limit one-host"]
B --> C["run for real --limit one-host"]
C --> D["run against the whole group"]
--list-tasks— confirm what will run.--check --diff --limit <one host>— preview the changes.- Run for real against that one host and verify it.
- Widen to the group once you're happy.