Skip to content

Ansible

Tool · prereq 03 (Containers/Podman) · ~1.5–2 days (4 submodules: inventory/vars, roles/collections, templating/handlers, vault/secrets)

A single host is easy to configure by hand — SSH in, install a package, edit a file, restart the service. A fleet isn't: the same steps repeated across ten or a hundred hosts, days apart, by whoever's on call — and configuration drifts the moment one host gets patched and another doesn't.

Ansible replaces that by-hand repetition with a playbook: declare the desired state once, push it over SSH with no agent to install on the target, and run it as many times as you want. A host already in that state reports no change — that's idempotency, and it's what makes "just run it again" a safe default instead of a gamble.

TL;DR — in 30 seconds:

  • Ansible is declarative and agentless: no daemon on the target — it SSHes in, runs a one-shot Python payload, and removes it; the control node holds all the brains.
  • Idempotency is the core guarantee: a converged host reports no changes on a second run, which is what makes "just run the playbook again" safe instead of risky.
  • A playbook binds hosts to tasks (each task = one module call); a handler fires only when a task reports changed, and secrets live encrypted in Ansible Vault.

Learning objectives

By the end, the junior can:

  • Write an inventory and an idempotent playbook.
  • Explain idempotency and why a second run is (mostly) green.
  • Use handlers and keep secrets in Ansible Vault.

Do it with Claude

Pair, Do and Prove run in Claude Code, with the onboarding-tutor skill. Click a button to copy the exact prompt, then paste it into Claude Code in this repo.

1. Learn · acquire the concept

Read: the ansible coursefocus §4 (static inventory), §6 (first playbook), §7 (common modules), §11 (handlers), §12 (idempotency), §15 (Vault). Skip for now: §14 (roles) and the AWS-static patterns — revisit when you operate a real fleet. Glossary (beat 5): the module’s Anki deck. Read next: Ansible documentation (official docs — full module index and the playbook/inventory reference).

Check yourself — you run the same playbook twice against a host that's already correctly configured. What should the second run report, and why does that matter?

All ok, zero changed — idempotency means Ansible detects the host already matches the desired state and skips re-applying it. If a second run keeps reporting changed on the same task, that task isn't actually idempotent, almost always because it's a command/shell call standing in for a real module.

Check yourself — why doesn't Ansible need an agent installed on every target host, unlike Chef or Puppet?

It's a push model over SSH — the control node connects, copies a one-shot Python payload into /tmp, executes it, and removes it. The target only needs SSH and Python; nothing is installed or registered on it ahead of time.

2. Pair · passive → active

Drive the onboarding-tutor skill:

  • "Explain idempotency with a concrete Ansible task example."
  • "3-line mental model of playbook → task → module."
  • "Quiz me on handler vs task before I write anything."

Common gotchas

  • Using command:/shell: where a real module exists. Fix: check first — a proper module reports changed/ok correctly and is idempotent for free; command/shell always reports changed unless you add creates:/changed_when: yourself.
  • Assuming a handler fires immediately when its task runs. Fix: a handler only fires when the notifying task reports changed, and only once at the end of the play regardless of how many tasks notify it.
  • Committing an unencrypted secret because "it's just a lab." Fix: Vault-encrypt anything secret before it's committed — once a plaintext secret is in git history, rotating the value doesn't erase it from history.
  • Skipping --check --diff before applying to a host you don't fully control. Fix: a dry run shows exactly what would change; skipping it on a shared fleet is how "it worked on my host" surprises production.

Done when: you can explain why re-running a good playbook is safe, unprompted.

3. Do · produce an artifact

Exercise — configure a host idempotently:

Target a container or a local VM (not a real server).

  1. Install Ansible first (course §3): pip install 'ansible-core>=2.16'.
  2. Write an inventory with your target.
  3. Write a playbook that installs a package, drops a templated config file, and restarts the service via a handler (only when the config changes).
  4. Run it. Then run it again — the second run should report no changes (except when you actually change the template).

Done when: the playbook converges, and a second run is green.

4. Prove · understanding gate

Mandatory. Pass bar in the Socratic gate.

  1. Walkthrough: Explain each task and why it's idempotent. When does your handler fire, and why not every run?
  2. Alternative: command/shell vs a proper module — why prefer the module? What breaks idempotency?
  3. Failure & debug: The second run keeps reporting "changed" on one task. What's likely wrong and how do you find it (--check, --diff)?
  4. Provenance: Which tasks did the agent write? How did you verify idempotency rather than assuming it?

Pass bar: you can defend idempotency and handler behaviour. A playbook that's "changed" every run and you can't explain why = back to Pair.

5. Retain · fight forgetting

Study the module deck SE Onboarding::08 Ansible — its 9 theme subdecks let you drill one area at a time. It syncs to your Anki automatically on pull (anki-sync).

Done when: you can recall the deck's key terms — playbook, inventory, module/task, facts & templating, idempotence — and have added a card for anything the gate exposed as weak.

Key takeaways

  • Ansible is agentless and push-based: the control node SSHes in, runs a one-shot Python payload, and removes it — nothing is installed or registered on the target ahead of time.
  • Idempotency is the whole safety model: a converged host reports zero changed on a second run; if a task keeps reporting changed, it isn't actually idempotent.
  • A playbook binds hosts to tasks, and each task is one module call. Prefer a real module over command/shell — a module reports changed/ok correctly and is idempotent for free, while command/shell always reports changed unless you add creates:/changed_when: yourself.
  • A handler fires only when its notifying task reports changed, and only once at the end of the play — not immediately when the task runs, and not once per notifying task.
  • Secrets are Vault-encrypted before they're committed, not after. Once a plaintext secret is in git history, rotating the value doesn't erase it from history.
  • --check --diff is the dry run that catches a bad playbook before it touches a shared host — skipping it on a fleet you don't fully control is how "it worked on my host" becomes a production surprise.

Go deeper — curated resources

Hand-picked external material to take this topic further — the best books, courses, talks, and writing. Cost is tagged Free, Free online (full text/course free on the web), or Paid.

  • Book · Ansible for DevOps — Jeff Geerling. The practical standard; the full manuscript reads free online under CC BY-SA, with a polished ebook/paperback for sale. Free online.
  • Video · Ansible 101 — Jeff Geerling. The best free video series, walking through the book in live tutorial form. Free.
  • Docs · Ansible Documentation — Red Hat / Ansible community. The full module index plus playbook and inventory reference. Free.
  • Repo · Ansible Galaxy — Ansible community. Community-published roles and collections you can pull into a playbook. Free.

Gate log (mentor fills on pass)

  • Date passed: / Passed by: / checked by: / Weak spots: