Terraform and Ansible are both infrastructure-as-code tools, but they solve different parts of the problem. Picking the wrong one for the wrong job is one of the most common DevOps mistakes — and combining both badly doubles the maintenance burden. This is the 2026 breakdown of when each tool wins and how to integrate them cleanly.
What changed in 2026
- OpenTofu reached Terraform feature parity — after HashiCorp's BSL relicensing in 2023, the CNCF-hosted OpenTofu project absorbed most of the open-source Terraform community. In 2026 the two are largely interchangeable syntactically, but OpenTofu has faster provider release cycles.
- Terraform CDK (CDKTF) stabilised — writing Terraform config in TypeScript or Python rather than HCL is now a viable production pattern for teams that want type safety.
- Ansible Execution Environments are standard — containerised Ansible runs are now the default in AWX and Ansible Automation Platform, eliminating dependency hell.
- Both tools added drift detection — Terraform's
plan drift reporting improved; Ansible added explicit drift-check mode in 2.17.
Core mental model
Terraform (and OpenTofu): Describe the desired state of your infrastructure. Terraform computes a diff against the current state file, then applies only the changes needed.
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.medium"
tags = {
Name = "web-server"
Env = "production"
}
}
Run terraform apply — Terraform handles create/update/destroy idempotently.
Ansible: Describe tasks to run against existing hosts. Ansible connects over SSH (or WinRM), runs each task, and reports changed/ok/failed per task.
- name: Install and start nginx
hosts: web_servers
become: true
tasks:
- name: Install nginx
ansible.builtin.package:
name: nginx
state: present
- name: Enable and start nginx
ansible.builtin.service:
name: nginx
state: started
enabled: true
Run ansible-playbook site.yml — Ansible configures what the host has installed and running.
Feature comparison
| Dimension |
Terraform / OpenTofu |
Ansible |
| Primary purpose |
Cloud resource provisioning |
Configuration management / app deployment |
| State model |
Explicit state file (remote backend) |
Agentless, desired-state per run |
| Language |
HCL or CDK (TS/Python) |
YAML playbooks + Jinja2 |
| Idempotency |
Native (state diff) |
Module-level, not always guaranteed |
| Cloud API coverage |
Excellent (providers for every cloud) |
Good via cloud modules, but lagging |
| OS config / packages |
Minimal (user_data scripts only) |
Core strength |
| Drift detection |
terraform plan shows drift |
Explicit check mode (--check) |
| Secrets management |
Vault, SOPS, or env vars |
Ansible Vault, or external |
| Learning curve |
Moderate |
Low for simple playbooks, high for roles |
How to pick
- Creating or destroying cloud infrastructure (VPCs, VMs, databases, DNS)? → Terraform.
- Installing packages, managing services, pushing config files? → Ansible.
- Deploying an application to existing servers? → Ansible.
- Need to manage Kubernetes manifests declaratively? → Neither — use Helm or ArgoCD instead.
- Want to provision a cloud VM and configure it? → Terraform provisions, Ansible configures. Use a Terraform
local-exec provisioner or a CI step to hand off.
Integration pattern
# Terraform: provision, then output the IP for Ansible
resource "aws_instance" "app" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.medium"
key_name = aws_key_pair.deployer.key_name
}
output "app_ip" {
value = aws_instance.app.public_ip
}
# CI pipeline step: run Ansible after Terraform
terraform apply -auto-approve
APP_IP=$(terraform output -raw app_ip)
ansible-playbook -i "$APP_IP," -u ubuntu site.yml
Common mistakes
Using Ansible to create cloud resources as a substitute for Terraform. Ansible cloud modules work, but they have no state file — you'll drift from reality without careful tagging and inventory management.
Storing Terraform state locally. Always use a remote backend (S3 + DynamoDB for AWS, GCS for GCP, Terraform Cloud, or Spacelift). Local state breaks team collaboration and loses history.
Giant monolithic playbooks. An Ansible playbook that does "everything" becomes unmaintainable at 500 lines. Break into roles, then compose.
Not pinning provider versions. Terraform providers release frequently; unpinned versions break plans silently after a provider update.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.50" # pin to minor version
}
}
}
What to skip
- Terraform for in-VM configuration — user_data bootstraps are one-shot; Ansible handles ongoing config much better.
- Ansible for immutable infrastructure — if you bake AMIs with Packer, Ansible inside the Packer build is fine, but don't run Ansible against running prod instances for anything mutable.
- CloudFormation / ARM if you have a multi-cloud requirement — Terraform/OpenTofu's provider model abstracts cloud differences; native tools lock you in.
FAQ
Should I use Terraform or OpenTofu in 2026?
If you are starting fresh: OpenTofu. It is MIT-licensed, CNCF-governed, and syntactically identical. If you already have a large Terraform Cloud estate, the migration cost may not yet justify switching.
Does Ansible replace Puppet or Chef?
For most teams, yes. Puppet and Chef are largely legacy; Ansible's agentless model and lower learning curve have displaced them except in large existing estates.
How do I handle secrets in Terraform?
Use Vault, AWS Secrets Manager, or SOPS. Never store secrets in .tfvars files in version control. The sensitive = true flag hides values from plan output but does not encrypt them at rest.
Is HCL hard to learn?
HCL is one of the easier domain-specific languages. Most engineers are comfortable after a week. CDKTF is worth considering if your team is deep in TypeScript and wants type-safe infra code.
Where to go next