Introduction
Most teams working in infrastructure automation eventually face the question: should we use Terraform or Ansible? The short answer is that they solve different problems. Terraform vs Ansible in Terraform, IaC & Cloud Automation represents a fundamental distinction between provisioning infrastructure and configuring servers—and understanding when to reach for each tool will save you months of debugging and rework.
In this day 79 installment of our 100-day series, we'll walk through where each tool shines, when immutable infrastructure changes the equation, and why mature teams often end up using both.
Terraform Creates Infrastructure
Terraform's job is to build the foundational layer: VPCs, subnets, security groups, databases, Kubernetes clusters, load balancers, and the compute instances themselves. It's declarative infrastructure as code that describes the desired end state of your cloud resources.
When you run terraform apply, Terraform:
- Reads your configuration files
- Compares them against the current state file
- Calculates a plan showing what will be added, modified, or destroyed
- Creates or updates resources via cloud provider APIs
Here's a practical example. Suppose we need an AWS EC2 instance within a VPC:
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
tags = {
Name = "production-vpc"
}
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
availability_zone = "us-east-1a"
tags = {
Name = "public-subnet"
}
}
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.medium"
subnet_id = aws_subnet.public.id
tags = {
Name = "web-server"
}
}Terraform handles the networking, IAM roles, security group rules, and instance lifecycle. It's excellent at orchestrating cloud resources at scale. What it doesn't do—by design—is install packages, configure applications, or manage the OS state once the machine is running.
Ansible Configures Servers
Ansible focuses on the operating system and application layer. It's an agentless tool that connects over SSH (or WinRM) and runs tasks to install software, manage configuration files, start services, and orchestrate application deployments.
When you run an Ansible playbook, it:
- Connects to target hosts
- Executes tasks idempotently (same result whether run once or ten times)
- Handles user management, package installation, and service configuration
- Supports templating for dynamic configuration
Let's say that EC2 instance needs Docker, Node.js, and a running application. Here's how Ansible handles it:
---
- hosts: web_servers
become: yes
vars:
app_version: "1.5.2"
tasks:
- name: Update package manager
apt:
update_cache: yes
cache_valid_time: 3600
- name: Install Docker
apt:
name: docker.io
state: present
- name: Install Node.js
apt:
name: nodejs
state: present
- name: Create app directory
file:
path: /opt/app
state: directory
owner: app
group: app
- name: Deploy application
copy:
src: dist/
dest: /opt/app/
owner: app
group: app
- name: Start application service
systemd:
name: app
state: started
enabled: yesAnsible excels at this layer. It's human-readable, runs scripts predictably, and can manage configuration across thousands of servers. But it assumes the servers already exist—you need Terraform (or CloudFormation, or Pulumi) to provision them first.
Immutable Images Reduce the Need for Ansible
Here's where things get interesting. Over the past five years, the industry has shifted toward immutable infrastructure—the practice of building complete server images (AMIs, container images) with all software pre-baked, then treating instances as disposable.
Instead of provisioning a blank EC2 instance and then using Ansible to configure it, we:
- Build a Docker image or AMI with Packer that includes Docker, Node.js, and application code
- Use Terraform to launch instances from that image
- Instances are ready to serve traffic immediately
- Updates mean building a new image and rolling out new instances
This approach has major advantages:
- Faster deployments — No configuration drift, no runtime surprises
- Easier testing — You test the exact image that runs in production
- Reduced complexity — Fewer moving parts mean fewer failure modes
- Simpler scaling — New instances come up fully configured
Here's a Packer template that builds an AMI with Docker and Node.js:
{
"builders": [
{
"type": "amazon-ebs",
"region": "us-east-1",
"source_ami_filter": {
"filters": {
"name": "ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"
},
"most_recent": true
},
"instance_type": "t3.micro",
"ami_name": "app-image-{{timestamp}}"
}
],
"provisioners": [
{
"type": "shell",
"inline": [
"sudo apt-get update",
"sudo apt-get install -y docker.io nodejs npm",
"sudo systemctl enable docker"
]
},
{
"type": "file",
"source": "dist/",
"destination": "/tmp/app"
},
{
"type": "shell",
"inline": [
"sudo mv /tmp/app /opt/app",
"sudo chown -R 1000:1000 /opt/app"
]
}
]
}Teams using immutable images often eliminate Ansible entirely in favor of Packer-built AMIs. Configuration happens at build time, not runtime. This is particularly powerful for containerized workloads where Docker images are built once and deployed everywhere.
Many Teams Use Both
Despite the immutable infrastructure trend, most mature production systems combine Terraform and Ansible strategically:
- Terraform provisions the cluster, networking, storage, and managed services
- Ansible handles complex OS-level configuration that doesn't fit into a single image
A realistic scenario: You're running a Kubernetes cluster on AWS. Terraform sets up the VPC, security groups, EKS cluster, and RDS database. Then Ansible connects to the cluster nodes (if self-managed) or target servers to install CNI plugins, configure kubelet settings, or apply host-level security hardening. For database servers that aren't containerized, Ansible might manage PostgreSQL configuration, replication, and backups.
Here's a hybrid workflow:
# Step 1: Terraform provisions infrastructure
terraform apply
# Step 2: Terraform outputs inventory for Ansible
output "web_servers" {
value = aws_instance.web[*].private_ip
}
# Step 3: Ansible configures servers
ansible-playbook -i inventory.yml site.ymlYou can automate this integration by having Terraform generate an Ansible inventory dynamically:
resource "local_file" "ansible_inventory" {
filename = "${path.module}/inventory.yml"
content = templatefile("${path.module}/inventory.tpl", {
web_servers = aws_instance.web[*].private_ip
})
}Then your inventory template references those Terraform outputs. When you run terraform apply, it updates the inventory automatically—no manual syncing required.
Common Mistakes
Using Ansible for infrastructure provisioning. Ansible can technically create cloud resources via cloud modules, but it's not idempotent in the same way Terraform is. If something fails halfway through, recovery is messy. Terraform's state management and planning capabilities are built for this job.
Using Terraform to configure servers. Terraform has remote-exec provisioners and local-exec provisioners, but they're a poor substitute for proper configuration management. They're fragile, hard to test, and don't handle idempotency well. Use Ansible, Chef, Puppet, or CloudInit instead.
Overcomplicating the handoff. If your Terraform output becomes a complex Ansible inventory, you're adding friction. Keep it simple: Terraform outputs a list of IPs or hostnames, Ansible consumes them. Consider whether immutable images would eliminate the Ansible step altogether.
Forgetting about state management. Ansible doesn't maintain infrastructure state like Terraform does. If you destroy resources manually or outside your IaC pipeline, Terraform gets confused. Treat your Terraform state file as a source of truth and never modify infrastructure outside your code.
Key Takeaways
- Terraform provisions cloud infrastructure (networks, instances, databases). Ansible configures servers and applications running on them.
- Immutable infrastructure (Packer + Terraform + container images) reduces or eliminates the need for Ansible by baking configuration into images at build time.
- Many teams use both: Terraform for infrastructure, Ansible for runtime configuration—but define clear boundaries to avoid overlap.
- Never use Terraform for OS-level configuration tasks. Never use Ansible as your primary infrastructure provisioning tool.
- Automate the Terraform-to-Ansible handoff by generating inventories dynamically from Terraform outputs.
- If most of your configuration is static or can be containerized, consider whether you need Ansible at all.






Comments
Loading comments…