Ad-hoc commands & running playbooks¶
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, plus the everyday ways to run the playbooks. Everything runs from ~/ansible/
on the control node. For the full list of ready-made commands for this homelab, see the
Quick Reference; when something fails, see
Troubleshooting.
Ad-hoc commands¶
An ad-hoc command runs a single module against hosts without writing a playbook. Format:
# 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 server1.example.com -a "docker ps"
ansible homelab -a "uptime"
# Use a specific module
ansible server1.example.com -m service -a "name=docker state=restarted"
ansible server1.example.com -m ansible.builtin.apt -a "name=htop state=present"
# Print a variable
ansible server1.example.com -m debug -a "var=docker_compose_dir"
# Print facts (ad-hoc runs don't gather facts, so use the setup module)
ansible server1.example.com -m setup -a "filter=ansible_distribution_release"
<target> is any inventory pattern: a host
(server1.example.com), 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.
First time setup (test connectivity)¶
# Ping all hosts to verify Ansible can reach them
ansible homelab -m ping
# Expected output for each host:
# server1.example.com | SUCCESS => {
# "changed": false,
# "ping": "pong"
# }
If any host fails, check:
- SSH key is copied to the host
- Host is reachable (
ssh -i ~/.ssh/ansible username@server1.example.com) - DNS resolves (
.example.comhostname works)
More fixes: Troubleshooting.
Running playbooks¶
Playbooks are what you actually run (see Playbooks for what's in them).
# Provision base system on all hosts
ansible-playbook playbooks/base.yml
# Deploy stacks only
ansible-playbook playbooks/stacks.yml
# Run everything (base + stacks)
ansible-playbook playbooks/site.yml
# Run only against server1
ansible-playbook playbooks/site.yml --limit server1.example.com
# Run only against apps group
ansible-playbook playbooks/site.yml --limit apps
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.
# Dry run the base playbook against all hosts
ansible-playbook playbooks/base.yml --check
# Preview the whole base playbook against one host
ansible-playbook playbooks/base.yml --check --limit server1.example.com
Add --diff to see the exact before/after of every file a template or
copy task would write:
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 server1.example.com # one host
ansible-playbook playbooks/base.yml --limit infra # one group
ansible-playbook playbooks/base.yml --limit '!nas.example.com' # everything except nas
Tags — run part of a play¶
Add tags to your playbooks for selective execution:
# In playbooks/base.yml, add tags to each role:
roles:
- { role: base, tags: ['base'] }
- { role: docker, tags: ['docker'] }
- { role: tailscale, tags: ['tailscale'] }
- { role: mounts, tags: ['mounts'] }
- { role: borgmatic, tags: ['borgmatic'] }
Then run or skip subsets:
# Only the docker role
ansible-playbook playbooks/base.yml --tags docker --limit server1.example.com
# 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 server1.example.com
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.
Ref: Ansible — Introduction to ad-hoc commands · Check mode · Tags