Ansible vs Terraform: Different Layers, Not Competitors

Quick answer
Terraform creates infrastructure and remembers what it created. Ansible configures machines and remembers nothing. That single difference — the state file — decides which tool belongs where, why Terraform provisioners are a trap HashiCorp themselves warn against, and why most serious setups run both.
- The distinction in one table
- The state file is the whole argument
- Where each one is genuinely better
- The overlap zone, and why both incursions go badly
- The pattern that actually works
10 min read · DevOps & Platform
Terraform creates infrastructure and remembers what it created. Ansible configures machines and remembers nothing. Almost every practical difference between them descends from that one sentence, and the teams who get into trouble are the ones who picked a side rather than a layer.
The question "Ansible or Terraform?" is usually the wrong question. The useful version is "which layer is this task on?" — because the tools barely overlap when used as designed, and both are actively bad at the other's job.
The distinction in one table
| Terraform | Ansible | |
|---|---|---|
| Primary job | Create, change and destroy infrastructure | Configure and orchestrate existing machines |
| Tracks what it did | Yes — a state file | No |
| Knows the plan before running | Yes — terraform plan | Partially — --check mode, best effort |
| Dependency handling | Builds a graph, orders automatically | Runs tasks in the order you wrote them |
| Can destroy what it built | Yes, reliably | No |
| Language | HCL, declarative | YAML, declarative tasks in explicit order |
| Agent needed | No | No |
| Typical target | Cloud APIs | Operating systems and services |
The state file is the whole argument
Terraform writes a state file recording every resource it manages and the real-world ID it maps to. This is not an implementation detail — it is the feature.
Because Terraform knows what it previously created, it can:
- Show you a plan.
terraform plandiffs your configuration against state and against reality, and tells you exactly what will be created, changed, or destroyed before anything happens. That is the single most valuable safety property in infrastructure tooling. - Destroy cleanly.
terraform destroyremoves what it built, in dependency order. Nothing else does this reliably. - Detect drift. If someone changes a security group in the console, the next plan surfaces it.
- Order operations automatically. Terraform builds a dependency graph from the references between resources. You do not tell it that the subnet must exist before the instance; it works that out.
Ansible has none of this, deliberately. It has no record of what it did. Ask Ansible to "undo the last playbook" and there is no such operation — you write a new playbook that reverses it, by hand.
For configuring a machine, statelessness is correct. Ansible does not need to remember that nginx is installed, because it can look. The desired state is checked against the live machine every run. But that same property makes Terraform's guarantees impossible.
Where each one is genuinely better
Terraform wins for anything with a lifecycle you care about. VPCs, subnets, load balancers, managed databases, IAM roles, DNS records, Kubernetes clusters. Things that get created once, changed occasionally, and must be destroyable. See Terraform for EKS infrastructure for what this looks like at scale.
Ansible wins for anything inside a machine, and for ordered operations across a fleet. Packages, config files, users, certificates, service restarts. And — underrated — orchestration: rolling restarts with serial:, draining a node before patching, running a migration on exactly one host before restarting the rest. Terraform has no vocabulary for "do this on one host, then those, in batches of five."
The overlap zone, and why both incursions go badly
Both tools can do the other's job. Both should not.
Terraform provisioners: a trap HashiCorp warns you about
Terraform has remote-exec and local-exec provisioners that run commands on a resource after creating it. It is tempting: create the instance and configure it in one apply.
HashiCorp's own documentation is unusually direct about this:
"You should exhaust all alternatives before using provisioners in your configurations."
And the reasoning:
"Terraform cannot predictably model provisioner behaviors represented in the configuration. Additionally, most provisioners require direct network access to your servers and credentials to install and configure external software, which introduces complexity and potential security issues."
Both objections are fatal in practice. Because Terraform cannot model what a provisioner does, provisioner effects never appear in a plan — you lose the property you adopted Terraform for. And a failed provisioner leaves the resource tainted, meaning the next apply destroys and recreates a perfectly good server because a shell command exited non-zero.
If your instances need configuration at boot, the right answers are: bake it into the image (Packer or equivalent), use cloud-init / user data, or hand off to a configuration tool after apply. Not provisioners.
Ansible's cloud modules: no plan, no destroy
Ansible ships modules for creating cloud resources. They work. Using them as your provisioning layer means giving up:
- The plan.
--checkmode is best-effort and many cloud modules do not support it meaningfully. You find out what happens by doing it. - Destroy. There is no
ansible destroy. Tearing down an environment means a reverse playbook you maintain by hand, and it will drift from the create playbook. - Dependency ordering. Ansible runs your tasks in the order you wrote them. You are the dependency graph. This is fine for ten resources and miserable for two hundred.
Ansible's cloud modules are genuinely useful for operations on existing infrastructure — snapshot a volume, tag some instances, look something up. As the thing that owns your VPC, they are a downgrade.
Terraform Day-2 Operations Checklist
State hygiene, drift, imports, policy checks, and upgrade routines — everything after `terraform apply` works. Plain Markdown, commit it to your repo.
Free. Instant download. You'll also get the occasional deep-dive from the newsletter — unsubscribe anytime.
The pattern that actually works
Terraform provisions. Ansible configures. The interesting question is how they hand off.
Use dynamic inventory, not Terraform output. The tempting approach is to have Terraform write an inventory file. It works and it rots — the file is a point-in-time snapshot, and the moment an autoscaling group replaces an instance it is wrong.
The durable pattern is that Terraform tags resources (role=web, env=prod), and Ansible's dynamic inventory plugin queries the cloud provider for hosts matching those tags at run time. Now the two tools share a contract — the tag — rather than a file. Terraform can replace every instance and Ansible still finds them.
Do not chain them in one command. Resist terraform apply && ansible-playbook. They have different failure modes and different retry semantics; a failed playbook should not leave you wondering about the state of an apply that already succeeded. Run them as separate, individually re-runnable steps.
Push configuration earlier where you can. The best Ansible run is the one that has almost nothing to do because the image was already built correctly. Using Ansible to build images (with Packer, or ansible-playbook against a build container) and then again for the small amount of per-host configuration is a stronger setup than configuring bare images at boot.
Do you need both?
Often not, and it is worth being honest about which cases are which.
Terraform only. You run containers on a managed platform — EKS, ECS, Cloud Run, App Runner. There are no long-lived VMs to configure, because configuration lives in the image. This describes a large and growing share of new systems, and adding Ansible to it is solving a problem you do not have. If your workloads are on Kubernetes, your configuration layer is Kubernetes.
Ansible only. You manage existing hardware, on-premises VMs, or network devices. Nothing is being provisioned via an API, so there is no lifecycle for Terraform to manage.
Both. You run VMs in a cloud. Terraform makes them, Ansible configures them. Still an extremely common shape.
What about OpenTofu?
Everything above applies identically to OpenTofu — it is a fork of Terraform, and the state-file, plan, and dependency-graph model is the same. The Ansible comparison does not change. If you are choosing between them, that is a licensing and governance question rather than a technical one; see OpenTofu vs Terraform.
Frequently Asked Questions
Can Terraform replace Ansible entirely?
Only if you have nothing to configure inside your machines — which is genuinely true for container-based platforms where configuration is baked into images. If you run VMs, Terraform's provisioners are not a substitute for a configuration tool, and HashiCorp explicitly advises exhausting alternatives before using them.
Can Ansible replace Terraform entirely?
Technically yes, practically no. You lose plan, reliable destroy, drift detection, and automatic dependency ordering. For a handful of resources that is survivable. Past that, you are hand-maintaining a dependency graph and a teardown playbook, and both will drift from reality.
Which should I learn first?
Terraform, if you are building infrastructure from scratch on a cloud. Ansible, if you are inheriting existing servers that need to be brought under control. Ansible has the gentler learning curve; Terraform has the higher ceiling for infrastructure work.
How do I pass Terraform outputs into Ansible?
Prefer not to pass them at all. Have Terraform tag resources and let Ansible's dynamic inventory discover hosts by tag at run time. If you genuinely need a value — a database endpoint, a bucket name — read it from a shared source both tools can query, such as a parameter store, rather than a generated file that can go stale.
Is Ansible declarative or procedural?
Both, in different senses, which is why the argument never settles. Individual tasks are declarative — you state the desired state and the module converges to it. But a playbook executes tasks in the order you wrote them, so the program is ordered in a way an HCL configuration is not. That ordering is a feature when you need orchestration and a liability when you wanted a dependency graph.
Does using both mean maintaining two sets of credentials?
It does, and it is worth designing for. Terraform typically needs broad cloud-API permissions; Ansible needs SSH access and a narrower cloud read permission for dynamic inventory. Keep them as separate identities with separate scopes — a compromise of the configuration pipeline should not grant the ability to destroy the infrastructure.
See also
- Nexus Repository Manager: Proxy Setup for npm, PyPI, Terraform, NuGet, Maven, and Docker
- What Is Ansible? — the execution model and vocabulary
- What Is Infrastructure as Code? — the category both tools belong to
- Terraform vs Pulumi — the other axis of this decision
- OpenTofu vs Terraform — the fork, and what changed
- Ansible for Kubernetes Automation — where Ansible fits once you are on Kubernetes
Official References
- Terraform documentation — configuration language, state and provider behaviour
- Terraform state — remote backends, locking and drift
- Ansible playbook guide — tasks, handlers, roles and execution order
Was this article helpful?
Be the first to rate this article
Related Topics
Found this useful? Share it.


