Ansible vs Terraform
Terraform and Ansible are two of the most popular Infrastructure as Code (IaC) tools — IaC means you describe your servers and infrastructure in text files instead of clicking around in a web console. People often ask “which one should I use?”, but that is usually the wrong question. They solve different problems, and the most common real-world setup uses both together. This page explains the difference in plain English, shows a side-by-side comparison, and gives you a clear recommendation for each situation.
The core difference: provisioning vs configuration
There are two distinct jobs when you set up infrastructure:
- Provisioning — creating the raw resources. This means spinning up a virtual server (a cloud computer you rent by the hour), a network, a load balancer (a server that spreads traffic across many machines), a database instance, and so on. You are bringing things into existence.
- Configuration management — taking a server that already exists and setting it up: installing packages with
apt, writing config files, starting services withsystemd, creating users. You are making an existing machine ready to do its job.
Terraform is built for provisioning. Ansible is built for configuration management. That single sentence explains 90% of the choice. Ansible can provision and Terraform can run scripts on a server, but each tool is much weaker outside its home turf.
Declarative vs procedural
A second big difference is how you write the instructions.
- Declarative means you describe the end state you want (“I want one server of this size in this region”), and the tool figures out the steps to get there. Terraform is declarative.
- Procedural means you describe the steps in order (“install nginx, then copy this file, then restart the service”). Ansible is mostly procedural — a playbook (a YAML file listing tasks) runs top to bottom.
This matters for a beginner because of idempotency — a fancy word that just means “running it twice does no harm”. A truly idempotent run only changes what is not already correct. Terraform is idempotent by design: it compares your desired state to reality and only changes the difference. Ansible is idempotent when you use its built-in modules (like apt, copy, service) instead of raw shell commands, because those modules check the current state before acting.
Comparison table
| Aspect | Terraform | Ansible |
|---|---|---|
| Primary job | Provisioning infrastructure | Configuring servers / apps |
| Style | Declarative (describe end state) | Mostly procedural (list of tasks) |
| Language | HCL (HashiCorp Configuration Language) | YAML playbooks |
| Tracks state? | Yes — keeps a terraform.tfstate file | No — checks live system each run |
| Idempotent? | Yes, built-in | Yes, when using modules (not raw shell) |
| Agent needed on target? | No (talks to cloud APIs) | No (connects over SSH) |
| Best at | Creating/destroying cloud resources | Installing packages, editing config files |
| Weak at | Detailed in-server setup | Creating cloud infra, tracking drift |
| Typical command | terraform apply | ansible-playbook site.yml |
Gotcha: Terraform’s state file (
terraform.tfstate) often contains secrets like database passwords in plain text. Never commit it to Git. Store it in a remote backend such as an S3 bucket with encryption and locking enabled.
The state file, explained
Terraform remembers what it built in a state file. Think of it as Terraform’s memory: a JSON record mapping “the server in my code” to “the actual server ID in the cloud”. This is what lets Terraform safely destroy or update exactly the resources it created, and detect drift (when someone changed something by hand). Ansible has no equivalent memory — every run it inspects the live machine fresh. Both approaches are valid; they just fit their jobs.
The common pattern: Terraform builds, Ansible configures
The mature, widely-used pattern is:
- Terraform creates the servers, networks, and firewall rules in the cloud.
- Terraform writes out the new server IP addresses (e.g. to an inventory file).
- Ansible connects over SSH to those servers and configures them — installs Nginx, sets up your app, hardens SSH, etc.
Here is a minimal Terraform file that creates one server and records its IP into an Ansible inventory:
resource "aws_instance" "web" {
ami = "ami-0abcdef1234567890"
instance_type = "t3.micro"
tags = { Name = "web-01" }
}
resource "local_file" "inventory" {
filename = "${path.module}/inventory.ini"
content = "[web]\n${aws_instance.web.public_ip} ansible_user=ubuntu\n"
}
Run the workflow on your Ubuntu 22.04/24.04 control machine. First install both tools:
sudo apt update
sudo apt install -y ansible
sudo snap install terraform --classic
Output:
ansible-core 2.16.x ...
terraform installed
Then provision with Terraform and configure with Ansible:
terraform init
terraform apply -auto-approve
ansible-playbook -i inventory.ini site.yml
Output:
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
PLAY [Configure web servers] ***************************
TASK [Install nginx] ***********************************
changed: [203.0.113.10]
PLAY RECAP *********************************************
203.0.113.10 : ok=4 changed=1 unreachable=0 failed=0
That site.yml is a normal Ansible playbook:
- name: Configure web servers
hosts: web
become: true
tasks:
- name: Install nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
- name: Ensure nginx is running
ansible.builtin.service:
name: nginx
state: started
enabled: true
Which should I use? Clear recommendations
| Your situation | Use |
|---|---|
| Spinning up cloud servers, VPCs, databases, DNS | Terraform |
| Installing software and config on existing servers | Ansible |
| One-off setup of a server you already rent | Ansible |
| Repeatable, destroyable cloud environments (dev/staging/prod) | Terraform |
| Pushing an app update or a config change to a fleet | Ansible |
| Full lifecycle: create and configure cloud servers | Both (Terraform then Ansible) |
Tip: Avoid using Terraform’s
provisioner "remote-exec"to do heavy in-server setup. It is fragile, runs only at create time, and is not idempotent. Hand that work to Ansible instead — that is exactly what it is for.
Best practices
- Use Terraform for what exists (infrastructure) and Ansible for what runs on it (software and config). Do not force one tool to do the other’s job.
- Store Terraform state remotely with encryption and locking; never commit
terraform.tfstateto Git. - In Ansible, prefer built-in modules (
apt,copy,template,service) over theshell/commandmodules so your runs stay idempotent. - Keep secrets out of both: use a vault (
ansible-vault) for Ansible and a secrets manager or environment variables for Terraform. - Generate your Ansible inventory from Terraform output so the two stay in sync automatically.
- Run both in CI/CD (automated pipelines) so the same commands work on every developer’s machine and the server.