Ansible vs Terraform: Configuration vs Infrastructure
Compare Ansible and Terraform for DevOps automation. Learn when to use each tool, how they differ in approach, and how to combine them effectively.
What you'll learn
- ✓How Terraform and Ansible differ in purpose and approach
- ✓Declarative vs procedural infrastructure management
- ✓When to use each tool and when to combine them
- ✓Practical examples of both tools solving the same problem
Prerequisites
- •Basic understanding of cloud infrastructure
- •Familiarity with YAML or HCL syntax
Two Tools, Different Jobs
Terraform and Ansible are both automation tools used in DevOps, but they solve different problems. Terraform is an infrastructure provisioning tool. It creates and manages cloud resources like servers, networks, databases, and load balancers. Ansible is a configuration management tool. It installs software, manages files, and configures systems that already exist.
The confusion arises because both tools can do some of each other’s work. Terraform can run scripts on servers after creating them. Ansible can create cloud resources using its cloud modules. But each tool is purpose-built for one side of the equation, and using the right tool for the right job gives you better results.
Core Differences
| Aspect | Terraform | Ansible |
|---|---|---|
| Primary purpose | Provision infrastructure | Configure systems |
| Language | HCL (HashiCorp Configuration Language) | YAML (playbooks) |
| Approach | Declarative | Procedural (with declarative modules) |
| State management | State file tracks resources | Stateless (no state file) |
| Agent requirement | None (API-driven) | None (SSH/WinRM) |
| Idempotency | Built-in via state comparison | Module-dependent |
| Execution | Plan then apply | Execute tasks sequentially |
| Drift detection | Yes (plan shows drift) | Limited |
Declarative vs Procedural
Terraform is declarative. You describe the desired end state and Terraform figures out what changes are needed:
# Terraform: "I want 3 EC2 instances"
resource "aws_instance" "web" {
count = 3
ami = "ami-0abcdef1234567890"
instance_type = "t3.medium"
tags = {
Name = "web-server-${count.index + 1}"
}
}
If you change count from 3 to 5, Terraform creates 2 more instances. If you change it from 5 to 3, Terraform destroys 2 instances. You never tell Terraform how to get there; you only describe where you want to be.
Ansible is procedural. You write a sequence of tasks that execute in order:
# Ansible: "Do these steps on the servers"
- name: Configure web servers
hosts: webservers
become: true
tasks:
- name: Install nginx
apt:
name: nginx
state: present
update_cache: true
- 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: true
handlers:
- name: restart nginx
service:
name: nginx
state: restarted
Ansible tasks run top to bottom. Each module (like apt, template, service) is individually idempotent, meaning running it twice produces the same result. But the overall playbook is a sequence of steps, not a state declaration.
State Management
Terraform maintains a state file that maps your configuration to real-world resources. This file is critical. It tells Terraform which resources it manages, what their current properties are, and what needs to change.
# View current state
terraform state list
# Output:
# aws_instance.web[0]
# aws_instance.web[1]
# aws_instance.web[2]
# aws_vpc.main
# aws_subnet.public
This state file enables powerful features:
# See what will change before applying
terraform plan
# Output:
# ~ aws_instance.web[0]
# instance_type: "t3.medium" -> "t3.large"
# Plan: 0 to add, 1 to change, 0 to destroy.
The downside is that state must be stored securely and shared across your team. Remote backends like S3, GCS, or Terraform Cloud solve this:
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/infrastructure.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
Ansible has no state file. Each time a playbook runs, Ansible connects to the target hosts, checks current conditions, and applies changes. This simplicity is an advantage for configuration management but a limitation for infrastructure provisioning, because Ansible cannot track what it previously created.
Where Each Tool Excels
Terraform Strengths
Infrastructure lifecycle management. Terraform tracks every resource it creates and can update or destroy them. Renaming a resource, changing its type, or tearing down an entire environment is straightforward:
# Destroy all infrastructure
terraform destroy
# Destroy a specific resource
terraform destroy -target=aws_instance.web[2]
Dependency graph. Terraform automatically determines the order in which resources must be created. A subnet depends on a VPC, a security group depends on the VPC, and an EC2 instance depends on both:
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id # implicit dependency
cidr_block = "10.0.1.0/24"
}
resource "aws_instance" "web" {
subnet_id = aws_subnet.public.id # implicit dependency
ami = "ami-0abcdef1234567890"
instance_type = "t3.medium"
}
Terraform creates the VPC first, then the subnet, then the instance. No manual ordering required.
Multi-cloud support. Terraform providers exist for AWS, GCP, Azure, Kubernetes, GitHub, Datadog, PagerDuty, and hundreds of other services. The same workflow applies everywhere.
Plan before apply. terraform plan shows exactly what will change before any action is taken. This is a safety net that Ansible lacks for infrastructure changes.
Ansible Strengths
System configuration. Ansible excels at tasks that happen inside a server: installing packages, managing users, deploying application code, configuring services, and managing files.
- name: Deploy application
hosts: app_servers
become: true
vars:
app_version: "2.5.0"
tasks:
- name: Create app user
user:
name: appuser
system: true
shell: /usr/sbin/nologin
- name: Download application
get_url:
url: "https://releases.example.com/app-{{ app_version }}.tar.gz"
dest: /opt/app/app.tar.gz
checksum: "sha256:abc123..."
- name: Extract application
unarchive:
src: /opt/app/app.tar.gz
dest: /opt/app/
remote_src: true
- name: Configure application
template:
src: app-config.yml.j2
dest: /opt/app/config.yml
owner: appuser
mode: '0644'
notify: restart app
- name: Ensure app service is running
systemd:
name: myapp
state: started
enabled: true
No agent required. Ansible connects over SSH (or WinRM for Windows). There is nothing to install on managed hosts. This makes it easy to start managing existing servers immediately.
Ad-hoc commands. Run one-off commands across your fleet without writing a playbook:
# Check disk space on all web servers
ansible webservers -m shell -a "df -h /"
# Restart a service everywhere
ansible all -m service -a "name=nginx state=restarted" --become
# Copy a file to all hosts
ansible all -m copy -a "src=hotfix.py dest=/opt/app/hotfix.py" --become
Inventory management. Ansible inventories can be static files or dynamic scripts that query your cloud provider:
# Static inventory
[webservers]
web1.example.com
web2.example.com
[databases]
db1.example.com
[webservers:vars]
nginx_worker_processes=4
# Dynamic inventory from AWS
ansible-inventory -i aws_ec2.yml --list
Solving the Same Problem with Each Tool
Let’s set up a web server with both tools to see the difference.
Terraform Approach
# main.tf - Create the infrastructure AND configure it
resource "aws_instance" "web" {
ami = "ami-0abcdef1234567890"
instance_type = "t3.medium"
key_name = "my-key"
vpc_security_group_ids = [aws_security_group.web.id]
user_data = <<-EOF
#!/bin/bash
apt-get update
apt-get install -y nginx
systemctl enable nginx
systemctl start nginx
echo "<h1>Hello from Terraform</h1>" > /var/www/html/index.html
EOF
tags = {
Name = "web-server"
}
}
resource "aws_security_group" "web" {
name = "web-sg"
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
Terraform creates the security group and EC2 instance. The user_data script runs on first boot to install nginx. This works, but the configuration is baked into a bash script inside the Terraform file. It is not reusable, testable, or easy to modify without recreating the instance.
Ansible Approach
Assuming the server already exists:
# playbook.yml
- name: Configure web server
hosts: web_servers
become: true
tasks:
- name: Install nginx
apt:
name: nginx
state: present
update_cache: true
- name: Deploy index page
copy:
content: "<h1>Hello from Ansible</h1>"
dest: /var/www/html/index.html
mode: '0644'
- name: Ensure nginx is running
service:
name: nginx
state: started
enabled: true
This is clean and reusable. You can run it repeatedly. If someone manually changes the index page, Ansible reverts it. But Ansible did not create the server or the security group.
Combining Terraform and Ansible
The best practice is to use both tools together. Terraform provisions infrastructure, then Ansible configures it.
Approach 1: Terraform Outputs as Ansible Inventory
# Terraform outputs the server IP
output "web_server_ip" {
value = aws_instance.web.public_ip
}
# After terraform apply, generate an Ansible inventory
terraform output -raw web_server_ip > inventory.ini
# Run Ansible against the new server
ansible-playbook -i inventory.ini playbook.yml
Approach 2: Dynamic Inventory
Use Ansible’s AWS dynamic inventory plugin to discover instances created by Terraform:
# aws_ec2.yml (Ansible inventory plugin)
plugin: aws_ec2
regions:
- us-east-1
filters:
tag:ManagedBy: terraform
tag:Role: webserver
keyed_groups:
- key: tags.Environment
prefix: env
ansible-playbook -i aws_ec2.yml playbook.yml
Approach 3: Terraform Provisioners (Use Sparingly)
Terraform can invoke Ansible directly, though HashiCorp considers provisioners a last resort:
resource "aws_instance" "web" {
ami = "ami-0abcdef1234567890"
instance_type = "t3.medium"
provisioner "local-exec" {
command = "ansible-playbook -i '${self.public_ip},' playbook.yml"
}
}
This couples the tools tightly and makes the Terraform apply slower. Prefer the inventory-based approaches.
Decision Guide
Use Terraform when:
- Creating, modifying, or destroying cloud resources.
- Managing infrastructure lifecycle (VPCs, databases, load balancers, DNS).
- You need a plan/apply workflow with drift detection.
- Working across multiple cloud providers.
Use Ansible when:
- Installing and configuring software on servers.
- Managing users, files, packages, and services.
- Running ad-hoc commands across a fleet.
- Deploying application code.
- Handling tasks that change frequently (config updates, deployments).
Use both when:
- You need infrastructure provisioning and system configuration.
- Terraform creates the servers; Ansible configures them.
- This gives you the best of both tools without fighting against their design.
Wrapping Up
Terraform and Ansible are complementary, not competing, tools. Terraform manages the lifecycle of infrastructure resources through a declarative state-based approach. Ansible manages the configuration of those resources through procedural task execution. Using Terraform for provisioning and Ansible for configuration gives you a clean separation of concerns, where each tool handles what it does best. The most effective DevOps teams adopt both tools and draw a clear line between infrastructure (Terraform) and configuration (Ansible).
Related articles
- DevOps Configuration Management: Ansible, Chef, and Puppet
Compare Ansible, Chef, and Puppet for configuration management. Learn how each tool works with practical examples for server provisioning and application deployment.
- DevOps Infrastructure as Code: Terraform Getting Started Guide
Learn Infrastructure as Code with Terraform. This beginner guide covers HCL syntax, providers, state management, modules, and deploying your first cloud resources step by step.
- DevOps Terraform vs Pulumi: Infrastructure as Code Compared
Compare Terraform and Pulumi for infrastructure as code. Understand the tradeoffs between HCL and general-purpose languages for managing cloud resources.
- DevOps Ansible Playbooks for Configuration Management
A hands-on guide to writing Ansible playbooks that configure servers, deploy apps, and enforce desired state across your fleet.