Inventory, ad-hoc commands, and playbooks
How Ansible manages many servers without installing any agent on them. · 11 min
Ansible is agentless — it connects to managed hosts over plain SSH (using the key-based auth from Level 9) and runs Python-based modules remotely, with nothing persistent to install or maintain on the target machines themselves. An inventory file lists the hosts (and groups of hosts) Ansible manages, typically at `/etc/ansible/hosts` or a project-specific file, grouping servers logically (`[webservers]`, `[databases]`) so commands and playbooks can target a specific group instead of every managed host.
An ad-hoc command runs a single module against a target group immediately, without writing a file — genuinely useful for a quick one-off task: `ansible webservers -m ping` checks connectivity to every host in the webservers group; `ansible webservers -a "systemctl status nginx" -b` runs a raw command (`-b` for "become", i.e. sudo) across every matching host and reports back the results from each.
A playbook is where Ansible's real value shows up: a YAML file describing a desired end state — "nginx should be installed and running", "this configuration file should have this exact content" — applied consistently across every targeted host. Critically, Ansible modules are idempotent by design: running the same playbook against a host that already matches the desired state does nothing and reports "ok" rather than an error or a duplicate action, which is what makes it safe to re-run a playbook repeatedly, including as a scheduled, unattended job, without worrying about cumulative side effects.
| Command | Purpose | Example |
|---|---|---|
| ansible all -m ping | Check connectivity to every host in the inventory | — |
| ansible webservers -a "uptime" | Run a raw ad-hoc command against a specific group | — |
| ansible-playbook site.yml | Run a playbook against its targeted hosts | — |
| ansible-playbook site.yml --check | Dry-run a playbook, showing what would change without actually changing anything | — |
A minimal, genuinely useful playbook
# site.yml
- name: Ensure Nginx is installed and running
hosts: webservers
become: true
tasks:
- name: Install nginx
apt:
name: nginx
state: present
- name: Ensure nginx is running and enabled
systemd:
name: nginx
state: started
enabled: trueCommon Mistakes
- ⚠ Forgetting `become: true` (or `-b` on ad-hoc commands) and having tasks silently fail or behave unexpectedly due to insufficient privileges
- ⚠ Writing a playbook task as an imperative shell command instead of using the appropriate idempotent module — this loses the safe-to-rerun property that's the whole point of Ansible
- ⚠ Running an untested playbook against production hosts without first using `--check` (dry-run) or testing against a single host
Quick Check
You run the same Ansible playbook twice in a row against a server that already matches the desired state. What should happen the second time?
Takeaway: Ansible modules are idempotent by design — that's what separates "safe to re-run automatically, anytime" from a raw shell script that might duplicate an action or error out the second time it runs.