Ansible and Terraform are not competitors. They solve different problems at different layers of the infrastructure lifecycle. But most comparison articles treat them as an either/or choice, which leads teams to pick the wrong tool — or worse, use one tool for everything.
We use both across every client engagement. Here is when we reach for each one, when we combine them, and the patterns that work in production.
The Core Difference in One Sentence
Terraform provisions infrastructure. Ansible configures what runs on it.
Terraform answers: “What cloud resources should exist?” — VPCs, EC2 instances, RDS databases, load balancers, Kubernetes clusters.
Ansible answers: “How should those resources be configured?” — install packages, deploy applications, manage users, apply security policies, rotate certificates.
This is not a theoretical distinction. It maps directly to operational phases:
| Phase | What Happens | Best Tool |
|---|---|---|
| Day 0 — Provisioning | Create cloud resources, networks, clusters | Terraform |
| Day 1 — Configuration | Install software, configure OS, deploy apps | Ansible |
| Day 2 — Operations | Patching, updates, compliance checks, rotation | Ansible |
| Lifecycle — Changes | Add/remove/modify infrastructure | Terraform |
| Lifecycle — Drift | Detect and fix configuration drift | Both |
How They Work Differently
Understanding the architectural differences explains why each tool is better at its job.
Declarative vs Procedural
Terraform is declarative. You describe the desired end state and Terraform figures out the steps:
# Terraform: "I want this to exist"
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.medium"
tags = {
Name = "web-server"
}
}
Terraform compares this definition against its state file, calculates the difference, and applies only what changed. If the instance already exists with matching configuration, Terraform does nothing.
Ansible is procedural. You describe the steps to execute in order:
# Ansible: "Run these steps on this server"
- name: Configure web server
hosts: web_servers
tasks:
- name: Install nginx
apt:
name: nginx
state: present
- name: Copy nginx config
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: restart nginx
- name: Ensure nginx is running
service:
name: nginx
state: started
enabled: yes
Ansible connects to each host via SSH (no agent required), runs the tasks in sequence, and reports what changed.
State Management
Terraform maintains a state file that tracks every resource it manages. This is both its strength (accurate change detection, dependency graphing) and its operational challenge (state locking, remote storage, secret exposure).
Ansible is stateless by default. It queries the current state of each system in real time. This means no state file to manage, but also no way to detect resources that were created outside of Ansible.
Provider Ecosystem
Terraform has 4,000+ providers covering every major cloud, SaaS, and infrastructure platform. It is the standard for cloud resource provisioning.
Ansible has 100+ collections with thousands of modules covering servers, network devices, security tools, cloud APIs, and application deployment. It is the standard for system configuration.
When to Use Terraform
Reach for Terraform when:
- Provisioning cloud infrastructure — VPCs, subnets, security groups, EC2 instances, RDS, S3 buckets, EKS clusters, IAM roles
- Managing Kubernetes resources — namespaces, RBAC, service accounts via the Kubernetes provider
- Multi-cloud deployments — same workflow across AWS, Azure, and GCP
- Infrastructure lifecycle — creating, updating, and destroying resources with a clear dependency graph
- Compliance tracking — state file provides a source of truth for what exists in your cloud accounts
Where Terraform Struggles
- OS-level configuration — installing packages, managing users, editing config files. Terraform’s
remote-execprovisioner can do this, but it is a hack, not a feature. - Application deployment — Terraform can deploy containers, but rolling updates, health checks, and rollback logic belong to orchestration tools.
- Mutable operations — certificate rotation, log rotation, security patching. These are ongoing operations, not infrastructure definitions.
When to Use Ansible
Reach for Ansible when:
- Server configuration — installing packages, configuring services, managing users and permissions
- Application deployment — deploying code, managing environment variables, running database migrations
- Security hardening — applying CIS benchmarks, configuring firewalls, managing SSH keys
- Compliance automation — running checks against security policies, generating audit reports
- Orchestration — coordinating multi-step workflows across multiple servers (rolling restarts, blue-green deployments)
- Network device management — configuring routers, switches, firewalls via SSH or API
Where Ansible Struggles
- Cloud resource provisioning — Ansible can create EC2 instances and S3 buckets, but it does not track state. If you run the same playbook twice, it queries AWS to check if resources exist, which is slower and less reliable than Terraform’s state-based approach.
- Dependency management — Terraform automatically builds a dependency graph. In Ansible, you manage ordering manually with task sequences.
- Scaling to hundreds of hosts — Ansible runs tasks sequentially by default. Large inventories need parallelisation tuning (
forks,serial,async).
When to Use Both (Our Default)
For most client projects, we use both tools together. Here is the pattern that works.
Pattern 1: Terraform Provisions, Ansible Configures
This is the most common pattern and the one we recommend for most teams.
┌─────────────────────────────────────────┐
│ Terraform │
│ - Creates VPC, subnets, security groups│
│ - Provisions EC2 instances │
│ - Creates RDS database │
│ - Outputs: instance IPs, DB endpoint │
└──────────────┬──────────────────────────┘
│ dynamic inventory
▼
┌─────────────────────────────────────────┐
│ Ansible │
│ - Installs packages (nginx, java, etc.)│
│ - Deploys application code │
│ - Configures monitoring agents │
│ - Applies security hardening │
└─────────────────────────────────────────┘
How the handoff works:
Terraform outputs the infrastructure details (IPs, hostnames, endpoints). Ansible picks them up using a dynamic inventory plugin:
# aws_ec2.yml — Ansible dynamic inventory
plugin: amazon.aws.aws_ec2
regions:
- eu-west-1
filters:
tag:Environment: production
tag:Role: web-server
keyed_groups:
- key: tags.Role
prefix: role
This inventory auto-discovers the instances Terraform created, so you never hardcode IPs.
Pattern 2: Terraform for Infrastructure, Ansible for Day 2
Even after the initial deployment, Ansible handles ongoing operations:
- Patching: Monthly OS and package updates across all servers
- Certificate rotation: Renew and deploy TLS certificates
- Configuration drift: Detect and fix servers that have drifted from desired state
- Compliance scans: Run CIS benchmark checks and remediate findings
Terraform does not handle these. If you try to manage patching through Terraform, you end up destroying and recreating instances, which is not appropriate for stateful workloads.
Pattern 3: Terraform for Cloud, Ansible for On-Premise
Many clients have hybrid environments — cloud infrastructure alongside on-premise servers or network devices. Terraform handles the cloud side. Ansible handles everything else, including bare-metal servers, network switches, and legacy systems that have no cloud API.
Real-World Integration Example
Here is a simplified version of what our CI/CD pipeline looks like when using both tools for a client’s AWS infrastructure:
# .github/workflows/deploy.yml
name: Infrastructure Deploy
on:
push:
branches: [main]
jobs:
terraform:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Terraform Init & Apply
run: |
cd terraform/
terraform init
terraform apply -auto-approve
- name: Export outputs for Ansible
run: |
cd terraform/
terraform output -json > ../ansible/tf_outputs.json
ansible:
needs: terraform
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Ansible playbook
run: |
cd ansible/
ansible-playbook -i aws_ec2.yml site.yml
Project Structure
infrastructure/
├── terraform/
│ ├── main.tf # VPC, EC2, RDS, etc.
│ ├── variables.tf
│ ├── outputs.tf # Exports IPs, endpoints
│ └── backend.tf # S3 state backend
├── ansible/
│ ├── aws_ec2.yml # Dynamic inventory
│ ├── site.yml # Main playbook
│ ├── roles/
│ │ ├── common/ # Base OS config
│ │ ├── nginx/ # Web server setup
│ │ ├── monitoring/ # Prometheus agent
│ │ └── security/ # Hardening
│ └── group_vars/
│ └── all.yml # Shared variables
└── .github/workflows/
└── deploy.yml # CI/CD pipeline
The Comparison Table
| Dimension | Terraform | Ansible |
|---|---|---|
| Primary use | Infrastructure provisioning | Configuration management |
| Approach | Declarative (desired state) | Procedural (ordered tasks) |
| Language | HCL | YAML |
| State | State file (tracks resources) | Stateless (queries live systems) |
| Agent | None (API-based) | None (SSH-based) |
| Cloud support | 4,000+ providers | Cloud modules available but limited |
| OS configuration | Minimal (provisioners) | Excellent (2,000+ modules) |
| Learning curve | Medium (HCL, state management) | Low (YAML, SSH) |
| Idempotency | Built-in (state comparison) | Module-dependent (most are idempotent) |
| Rollback | Destroy and recreate | Manual (no built-in rollback) |
| Secret management | State file exposure risk | Ansible Vault built-in |
| Drift detection | terraform plan | Custom checks required |
| Best for | Cloud resources lifecycle | Server config, app deployment |
| License | BSL 1.1 (or OpenTofu MPL 2.0) | GPL 3.0 (open source) |
Our Decision Framework
What are you automating?
├── Cloud resources (VPC, EC2, RDS, K8s, IAM)?
│ └── Use Terraform (or OpenTofu)
│
├── Server configuration (packages, services, users)?
│ └── Use Ansible
│
├── Application deployment?
│ └── Use Ansible (or Kubernetes-native tools)
│
├── Both infrastructure AND configuration?
│ └── Use both: Terraform provisions, Ansible configures
│
├── On-premise or network devices?
│ └── Use Ansible (SSH/API access, no cloud API needed)
│
└── Already using one tool for everything?
├── Terraform doing OS config? → Add Ansible
└── Ansible doing cloud provisioning? → Add Terraform
Common Mistakes We See
1. Using Terraform for everything
Teams use remote-exec provisioners to install packages and configure servers through Terraform. This breaks on every apply because provisioners only run on resource creation, not updates. When the server needs patching six months later, Terraform cannot help.
2. Using Ansible for cloud provisioning
Ansible can create AWS resources, but without a state file it cannot detect drift or plan changes. Teams end up with “phantom resources” — infrastructure that Ansible created but lost track of because someone modified it manually.
3. Duplicating work between tools
Both tools can create security groups. Both can manage IAM roles. Pick one tool per resource type and stick to it. Our rule: if it is a cloud API resource, Terraform owns it. If it is an OS-level or application-level concern, Ansible owns it.
4. Not using dynamic inventory
Teams hardcode IP addresses in Ansible inventories, then wonder why playbooks break when Terraform replaces an instance. Use dynamic inventory plugins that auto-discover hosts based on tags or labels.
What About Kubernetes?
If your infrastructure is primarily Kubernetes-based, the picture changes:
- Terraform provisions the cluster (EKS, AKS, GKE)
- Helm/Kustomize configures what runs inside the cluster (replaces Ansible for K8s workloads)
- ArgoCD handles GitOps-based continuous deployment
In this model, Ansible’s role shrinks to managing nodes that are not inside Kubernetes — bastion hosts, CI runners, monitoring servers, and legacy systems.
Getting Started
If you are currently using only one tool:
Adding Ansible to a Terraform project:
- Create an
ansible/directory alongside yourterraform/directory - Set up dynamic inventory for your cloud provider
- Start with a simple playbook that installs monitoring agents on all servers
- Expand to application deployment and security hardening
Adding Terraform to an Ansible project:
- Identify cloud resources that Ansible currently creates (EC2, VPCs, S3)
- Import them into Terraform state with
terraform import - Remove the Ansible tasks that provision cloud resources
- Keep Ansible for everything that happens after the instance boots
Need Help Setting Up Your Automation Pipeline?
We build infrastructure automation pipelines that combine Terraform and Ansible for the full lifecycle — from cloud provisioning to application deployment and ongoing operations.
Our Terraform consulting services cover:
- IaC architecture — design Terraform + Ansible pipelines that scale with your team
- Migration — move from Ansible-only or Terraform-only setups to a combined approach, including OpenTofu migration
- CI/CD integration — automate the full pipeline in Jenkins or GitHub Actions
- Day 2 automation — patching, compliance scanning, and drift detection with Ansible
We have set up automation pipelines for teams managing 50 to 2,000+ cloud resources across AWS, Azure, and GCP.