Terraform and Ansible get compared constantly, and the comparison is a little misleading because they mostly do different jobs. Terraform stands up infrastructure: it talks to AWS, GCP, Azure, and hundreds of other APIs to create servers, networks, databases, DNS records, and load balancers, then remembers what it created. Ansible configures machines that already exist: it connects over SSH, installs packages, writes config files, and restarts services.
You can force either tool to do the other's job, and people do, but the results are awkward. The more useful framing is to understand what each is built for, see them side by side, and then decide whether you need one, the other, or both. For a lot of teams the answer is both.
| Terraform | Ansible | |
|---|---|---|
| Primary job | Provision infrastructure | Configure existing machines |
| Model | Declarative (describe end state) | Procedural (ordered tasks) |
| State | Keeps a state file | Stateless, checks live at run time |
| Language | HCL | YAML (playbooks) |
| How it connects | Cloud provider APIs | SSH (agentless), WinRM on Windows |
| Typical output | New VMs, VPCs, databases, DNS | Installed software, edited files, running services |
| Idempotency | Built into the resource graph | Per-module, up to each task |
| Owned by | HashiCorp (now IBM) | Red Hat (IBM) |
Two words carry most of the difference: provisioning and configuration.
Provisioning is creating the resource in the first place. There was no database, now there is a db.t3.medium running Postgres in us-east-1. Terraform is a provisioning tool.
Configuration management is taking a machine that exists and getting it into the right shape: the right nginx version, the right kernel settings, the right systemd units enabled. Ansible is a configuration management tool.
The other big difference is state. Terraform writes a state file that maps your configuration to the real resources it created. That is how terraform plan can tell you "this change will destroy one instance and create two" before you apply it, and how terraform destroy knows exactly what to tear down. Ansible has no state file. It connects to a machine, checks the current condition of each thing it manages, and makes only the changes needed. Both approaches are idempotent in practice, but they get there differently: Terraform reconciles against recorded state, Ansible re-checks reality every run.
Say you want a web server. In Terraform, you describe the resource you want to exist and let it reconcile.
# main.tf: provision the machine
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.small"
tags = {
Name = "web-1"
}
}
resource "aws_security_group" "web" {
name = "web-sg"
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
Run terraform plan and it shows you what will change. Run terraform apply and the EC2 instance and security group get created, and both are recorded in state. Change instance_type to t3.medium later, apply again, and Terraform works out that it needs to modify (or replace) the existing instance rather than making a second one. That reconciliation is the whole point of the state file.
Terraform stops at "the server exists." It does not know or care whether nginx is installed on it.
That is where Ansible picks up. An Ansible playbook is a list of tasks that run in order against a target host.
# webserver.yml: configure the machine
- hosts: web
become: true
tasks:
- name: Install nginx
apt:
name: nginx
state: present
update_cache: true
- name: Write the site config
copy:
src: files/site.conf
dest: /etc/nginx/sites-available/default
- name: Enable and start nginx
service:
name: nginx
state: started
enabled: true
Run this with ansible-playbook webserver.yml and it SSHes into every host in the web group and runs the three tasks top to bottom. Each task is idempotent: if nginx is already installed, the apt task reports ok instead of changed and moves on. But you wrote the steps as an ordered sequence, and order matters (you install before you configure before you start). That sequencing is what makes Ansible procedural even though individual modules are safe to re-run.
Notice what neither tool did. Terraform never touched the OS. Ansible never created a server; it assumed web hosts already exist and are reachable over SSH.
Because they cover different stages, the common production pattern is a handoff. Terraform provisions, Ansible configures.
# Terraform provisions and writes an Ansible inventory
resource "aws_instance" "web" {
count = 3
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.small"
tags = { Name = "web-${count.index}" }
}
resource "local_file" "inventory" {
content = templatefile("inventory.tmpl", {
ips = aws_instance.web[*].public_ip
})
filename = "inventory.ini"
}
Terraform creates three instances and writes their IPs into an inventory file. Then Ansible reads that inventory and configures all three:
terraform apply ansible-playbook -i inventory.ini webserver.yml
There are tighter integrations (Ansible's amazon.aws dynamic inventory can query AWS directly, and tools like Terragrunt or CI pipelines chain the two steps), but the shape is always the same. Terraform owns "what exists," Ansible owns "how it is set up." Neither tool tries to do the other's job well, so you keep the boundary clean.
This is why the "vs" framing undersells it. For a fleet of long-lived VMs, you very often want both.
Ansible gets unfairly dismissed by people who live in a fully managed-cloud world. It earns its keep in real situations:
serial keyword and ordered tasks fit this well.ansible web -a "systemctl restart nginx" across a hundred hosts, right now, without writing a playbook.Terraform does none of these well. Its provisioners exist but the docs themselves call them a last resort, and it has no model for ongoing OS drift.
Equally, Ansible is a poor fit for provisioning cloud resources at scale, and Terraform is built for exactly that:
terraform plan gives you a reviewable diff before anything happens. The state file makes teardown exact.Ansible can call cloud modules to create resources, but it has no state file, so it cannot cleanly tell you "these three things will be destroyed" or reliably tear down exactly what it made. Provisioning is not what it is for.
Reach for Terraform when the task is creating and destroying cloud infrastructure, you want a plan/apply review step, and you need an exact record of what exists so you can change or tear it down safely.
Reach for Ansible when the machines already exist and you need to configure them, deploy applications, patch and manage them over time, or run one-off commands across a fleet, especially over SSH to hosts you did not provision through an API.
Reach for both when you run long-lived VMs in the cloud. Terraform builds them, Ansible sets them up and keeps them in shape. This is the mainstream pattern and it works because the boundary is clean.
There is a category where neither is the obvious answer: a modern application backend where most of the "infrastructure" is application-level (a Postgres database, a Pub/Sub topic, a cron job, an object storage bucket) and there is no long-lived VM to configure at all. If you are on managed cloud services and containers, you may have very little for Ansible to do, and your Terraform is mostly wiring managed resources to application code that lives in a different repo and knows nothing about them.
That gap is what Encore, the open-source backend framework, addresses: you declare those resources directly in your TypeScript or Go code and Encore Cloud provisions the real services in your own AWS or GCP account, no separate HCL and no separate state file to keep in sync. It does not replace Ansible's server configuration job or Terraform's role in large VM and networking estates, and teams that need custom VPC topologies or resources outside its primitives still reach for Terraform or Pulumi. It is worth a look only if your backend is application-shaped rather than fleet-shaped.
import { SQLDatabase } from "encore.dev/storage/sqldb";
import { CronJob } from "encore.dev/cron";
// The database and the schedule are declared in the app code that uses them.
const db = new SQLDatabase("orders", { migrations: "./migrations" });
new CronJob("reconcile", { every: "1h", endpoint: reconcile });
Because the infrastructure lives in the application code, an AI agent reading the repo sees the database, the cron job, and the endpoints in one place. There is no separate HCL repo it has to cross-reference and keep in sync. Encore also ships an MCP server and encore llm-rules init, which give agents the schema, traces, and architecture as context, so agent output follows the same conventions and stays deployable.
Declare Postgres and a cron job in TypeScript and deploy to your own AWS or GCP account, with no separate state file to manage.
Terraform provisions infrastructure and tracks it in state. Ansible configures machines that already exist and re-checks them every run. They sit at different stages of the same lifecycle, which is why so many teams run both. Pick based on the task in front of you. If you are creating cloud resources, that is Terraform. If you are setting up or maintaining machines that already exist, that is Ansible. If you run a fleet of long-lived VMs, you will usually want both working in sequence.
Terraform provisions infrastructure (it creates and destroys cloud resources like servers, databases, and networks) and tracks what exists in a state file. Ansible configures and manages machines that already exist (installing packages, editing files, restarting services). One builds the infrastructure, the other sets it up.
Yes, and many teams do. A common pattern is Terraform provisions the VMs and networking, then hands the server IPs to Ansible, which installs software and applies configuration. They cover different stages of the same lifecycle.
Ansible is mostly procedural: a playbook is an ordered list of tasks that run top to bottom. Individual modules are idempotent (running them twice is safe), but you still write the steps in sequence. Terraform is declarative: you describe the end state and it works out the changes.
No. Ansible does not keep a state file. It checks the current condition of each machine at run time and applies whatever changes are needed. Terraform keeps a state file that maps your config to real resources, which is how it knows what to update or delete.
If your job is standing up cloud infrastructure (VMs, VPCs, managed databases), start with Terraform. If your job is configuring existing servers (app deployment, package management, OS setup), start with Ansible. Many infrastructure roles use both.
Not well. Terraform can run a provisioner or a startup script, but that is discouraged for anything beyond bootstrapping. It has no concept of ongoing configuration drift on the OS. That is Ansible's job.