AnsibleXLII · Ansible Beyond Linux ServersBeyond Linux servers
Provisioning versus configuration
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
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:
| Property | Ansible provisioning | State-based provisioning |
|---|---|---|
| Re-running is safe | Yes, via idempotent modules | Yes, via plan and apply |
| Resource is removed when you stop declaring it | No | Yes |
| A reviewable plan before any change | Partial: --check, where the module supports it | Yes, as the normal workflow |
| Knows which resources it owns | No | Yes |
| Dependency ordering | Task order you wrote | Derived from references |
| Configures the guest afterwards | Yes, natively | No, 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.
- The provisioning tool creates infrastructure and records it in state.
- It publishes outputs — addresses, names, roles, environment tags.
- Ansible builds inventory from those outputs, either through a dynamic inventory source that queries the provider, or from a file the pipeline generates.
- 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:
ansible-inventory -i inventory/generated.yml --graph
ansible-playbook -i inventory/generated.yml site.yml --list-hostsKnowledge check
Knowledge check · 4 questions
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?
Q2. What is the main design objection to running a provisioning tool as a task inside a playbook, using a module that wraps it?
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.
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.