Skip to main content
RunBook Academy

AnsibleXLII · Ansible Beyond Linux ServersBeyond Linux servers

Provisioning versus configuration

Advanced⏱ ~26 minansible-coreansible-inventory

What you'll learn

  • Separate provisioning from configuration by asking who owns a resource existence
  • Explain what a provisioning state model provides that idempotency alone does not
  • Predict what happens to a resource when the task that created it is deleted from a playbook
  • Design the hand-off where provisioning outputs feed configuration inventory
  • Recognise the failure where two tools each believe they own the same resource

Prerequisites

Verified against ansible-core 2.21.x · ansible (community package) 14.x · Python (controller) 3.12+ · ansible-lint 26.x · Molecule 26.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-11

Not yet marked complete on this device.

The argument about whether to use Ansible or a provisioning tool is usually had in terms of preference, ecosystem or team familiarity. It has a better form, and it is a single question:

Who owns the fact that this resource exists?

Everything else follows. If a tool owns a resource’s existence, it must be able to answer “which resources did I create?” — and that answer is not derivable from a playbook, however idempotent the playbook is.

This lesson is about that gap. It is deliberately not a tour of a competing tool, and it does not assume you use one. The reasoning applies equally to Terraform, to a cloud provider’s own stack service, and to the decision to use neither.

Idempotency is not state, and the difference is deletion

Ansible’s model is convergence. A task describes a desired condition, the module inspects reality, and it acts only if reality differs. Run it twice and the second run does nothing. That is idempotency, and it is genuinely excellent for configuration.

Notice what it does not include: any record of what happened. When a module creates a virtual machine, nothing is written down anywhere saying “this run created VM 412”. The next run rediscovers the situation from scratch by asking the API what exists.

Which is fine, until you delete the task.

# Removed in commit a1b2c3d, "retire the legacy reporting service"
# - name: Reporting VM
#   community.proxmox.proxmox_kvm:
#     name: legacy-reporting
#     state: present

Delete that task, run the playbook, and it succeeds. The VM continues to exist, consuming storage, appearing in inventory, receiving patches, and being backed up, forever. Nothing in the run mentions it, because nothing in the playbook mentions it. The playbook is a list of assertions about things that should be true; it has no way to express “and nothing else”.

A provisioning tool with a state model behaves differently, and this is the entire point of the state model. It knows it created that resource, notices the resource is no longer declared, and includes its destruction in the plan.

What “Ansible can provision, and it works” really means

It can, and it does. proxmox_kvm, the cloud collections, the container and network modules — all of them will create resources, idempotently, and many teams run exactly that in production without incident.

The honest framing is not that it is wrong. It is that you are trading away the state model, and the trade should be conscious:

PropertyAnsible provisioningState-based provisioning
Re-running is safeYes, via idempotent modulesYes, via plan and apply
Resource is removed when you stop declaring itNoYes
A reviewable plan before any changePartial: --check, where the module supports itYes, as the normal workflow
Knows which resources it ownsNoYes
Dependency orderingTask order you wroteDerived from references
Configures the guest afterwardsYes, nativelyNo, needs a second tool

The right-hand column is not free either. A state file is an operational object with its own failure modes: it can be lost, it can be corrupted, it can be locked by an interrupted run, it contains sensitive values, and two people applying at once is its own incident class. Teams that adopt state inherit the job of looking after it.

So the reasonable positions are:

  • A small estate with a handful of long-lived resources, no state tooling in the organisation, and a team that already runs Ansible — use Ansible, and write down that deletions are manual.
  • A fleet where resources are created and destroyed regularly, or several teams share an account — the state model earns its cost, and Ansible should stop at the guest boundary.
  • Bootstrapping, one-off labs, and the controller itself — Ansible, without apology. You cannot use the provisioning pipeline to build the machine that runs the provisioning pipeline.

Wrapping the provisioning tool usually inverts the dependency

community.general.terraform exists. Its parameters are real — project_path, state with choices planned, present and absent, plan_file, workspace, force_init, binary_path, lock, lock_timeout and others — and it will run a provisioning workflow as a task inside a play.

It is a legitimate module and there are narrow cases for it. In the common case it produces a design that is worse than either tool alone, for three reasons.

The review artefact disappears. The provisioning workflow’s value is that a human reads the plan and approves it before apply. Wrapped in a task, the plan is computed and applied inside a single ansible-playbook invocation. What gets reviewed is the playbook, not the plan — and the plan is the thing that says “this will destroy your database”.

Two locking models collide. The provisioning tool serialises applies with its own lock. Ansible has no idea that lock exists. Two runs of the same play — a scheduled one and a person’s — now interact through a mechanism neither tool is reporting on, and the symptom is a task that hangs until lock_timeout.

The failure surface doubles. A failed run now needs the reader to determine whether the play failed, the wrapper failed, the provisioning tool failed, or the provider failed, from output that has been through two layers of formatting.

The narrow cases where wrapping is reasonable share a shape: the provisioning step is small, fully owned by the same repository, and genuinely must happen inside a larger orchestration — for instance building an ephemeral test environment that is created and destroyed within one pipeline run, where nobody was going to review a plan for a thing that lives eleven minutes.

The hand-off that works: outputs become inventory

The healthy arrangement keeps both tools doing what they are good at and connects them at exactly one point.

  1. The provisioning tool creates infrastructure and records it in state.
  2. It publishes outputs — addresses, names, roles, environment tags.
  3. Ansible builds inventory from those outputs, either through a dynamic inventory source that queries the provider, or from a file the pipeline generates.
  4. Ansible configures the guests, knowing nothing about how they came to exist.

The seam is one-directional and narrow, which is what makes it survivable. Ansible never writes to the provisioning tool’s state; the provisioning tool never reaches inside a guest.

Two implementations, with different trade-offs:

Query the provider directly. A cloud or platform inventory plugin asks the API what exists and groups by tag. This is the arrangement the dynamic inventory lessons describe, and it has the advantage of being true at the moment the play runs. Its cost is that it shows you everything in the account, including resources this pipeline did not create — so the grouping and the --limit discipline are doing real safety work.

Generate a file from outputs. The pipeline writes a static inventory after a successful apply. This has the advantage of describing exactly what was provisioned, and the disadvantage of going stale the instant anything changes outside the pipeline.

Either way, inspect it before running anything against it:

Read-only / Safethe same first step, whatever generated the inventory
ansible-inventory -i inventory/generated.yml --graph
ansible-playbook -i inventory/generated.yml site.yml --list-hosts

Knowledge check

Knowledge check · 4 questions

  1. Q1. A team removes the task that created a virtual machine from their playbook and runs it again. The run is green. What has happened to the VM?

  2. Q2. What is the main design objection to running a provisioning tool as a task inside a playbook, using a module that wraps it?

  3. Q3. A firewall rule is declared in a provisioning configuration and also created by an Ansible task. Which of these follow? Select all that apply.

  4. Q4. Provisioning with Ansible instead of a state-based tool is a legitimate choice, provided the team writes down that removing a declaration does not remove the resource.

Passing score: 75%. Answers are checked in this browser.