
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
Choosing between infrastructure tools is rarely about which is "better" in isolation; it is about matching the tool to the specific lifecycle phase of your systems. The debate around Ansible vs Terraform: Config vs Provisioning often stems from overlapping capabilities, but in production environments, these tools serve distinct, complementary purposes. Misapplying them leads to fragile state management, slow deployments, or compliance gaps during audits. This guide cuts through the noise to define exactly where each tool belongs in a modern, secure stack.
How do you distinguish Ansible vs Terraform: Config vs Provisioning in practice?
The fundamental difference lies in their primary design philosophy and execution model. Terraform is an infrastructure provisioning tool built on a declarative model. You describe the desired end state of your cloud resources—such as an AWS VPC, an Azure Kubernetes Service cluster, or a GCP Cloud SQL instance—and Terraform calculates the dependency graph to create, update, or destroy those resources via provider APIs. It maintains a state file that tracks the real-world mapping of your configuration to actual infrastructure, making it authoritative for "what exists."
Ansible, conversely, is a configuration management tool that operates procedurally (though modules strive for idempotence). It connects to existing servers over SSH or WinRM to execute tasks: installing packages, managing users, templating configuration files, and restarting services. While Ansible has cloud modules, they lack the sophisticated state tracking and dependency resolution of Terraform for complex infrastructure graphs. As detailed in my practical guide to Infrastructure as Code with Terraform, treating Terraform as the source of truth for infrastructure topology prevents drift and simplifies disaster recovery.
In practice, this means Terraform answers "Do we have three EC2 instances in us-east-1a behind an ALB?" while Ansible answers "Are those instances running nginx 1.25, hardened per CIS benchmarks, with the correct application config?" Blurring these lines creates technical debt. Using Ansible to provision VPCs leads to unmanageable state; using Terraform to manage file permissions results in brittle, slow applies. For teams building internal platforms, understanding this boundary is critical before exploring platform engineering patterns.
When should you use Terraform for infrastructure provisioning?
Terraform excels when you need to manage cloud-native resources with complex dependencies, lifecycle policies, or multi-cloud footprints. Its strength is the plan/apply cycle, which provides a safety net by showing exactly what will change before execution. This is non-negotiable for compliance frameworks like SOC 2 or ISO 27001, where auditors require evidence of controlled, reviewed infrastructure changes.
Core provisioning scenarios
- Network Topology: VPCs, subnets, route tables, NAT gateways, and peering connections. These resources are highly interdependent; Terraform’s DAG (Directed Acyclic Graph) engine handles creation order automatically.
- Managed Services: RDS/Aurora clusters, ElastiCache, S3 buckets with lifecycle rules, and IAM policies. Provider support is deep and maintained by vendors.
- Kubernetes Clusters: EKS, AKS, or GKE control planes. Terraform provisions the cluster; it does not manage workloads inside it (use Helm or ArgoCD for that).
- Multi-Account/Org Setup: AWS Organizations OUs, SCPs, and cross-account IAM roles. This foundational layer must be version-controlled and state-managed.
# main.tf - Terraform provisioning example
resource "aws_instance" "web" {
count = 3
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
subnet_id = aws_subnet.private.id
tags = {
Name = "web-server-${count.index}"
Environment = "production"
ManagedBy = "terraform"
}
}
output "instance_ips" {
value = aws_instance.web[*].private_ip
} A common mistake is trying to use Terraform for application deployment. Terraform can trigger a Lambda or run a remote-exec script, but these are anti-patterns for ongoing configuration. They bypass Ansible’s strengths in idempotent task execution and inventory management. Keep Terraform focused on the substrate; let configuration tools handle the software layer.
When should you use Ansible for configuration management?
Ansible is the right choice once infrastructure exists and needs to be configured, secured, or updated. Its agentless architecture (SSH-based) makes it ideal for brownfield environments, hybrid setups, or any scenario where installing agents is prohibited. Unlike provisioning tools, Ansible shines at procedural workflows that require conditional logic, looping, and interaction with running systems.
Configuration management sweet spots
- OS Hardening: Applying CIS benchmarks, configuring firewalls (UFW/nftables), managing SSH keys, and setting up fail2ban. See my Ubuntu security hardening guide for concrete playbook examples.
- Application Deployment: Installing runtime dependencies, templating config files with Jinja2, managing systemd services, and performing rolling restarts.
- User & Access Management: Creating system users, managing sudoers, rotating credentials, and syncing with LDAP/AD.
- Patch Management: Running apt/yum updates, rebooting servers safely in batches, and verifying service health post-patch.
# site.yml - Ansible configuration example
- name: Harden web servers
hosts: webservers
become: true
tasks:
- name: Install nginx
ansible.builtin.apt:
name: nginx=1.24.*
state: present
update_cache: true
- name: Deploy custom nginx config
ansible.builtin.template:
src: templates/nginx.conf.j2
dest: /etc/nginx/nginx.conf
mode: '0644'
notify: Restart nginx
- name: Ensure firewall allows HTTP
community.general.ufw:
rule: allow
port: '80'
proto: tcp
handlers:
- name: Restart nginx
ansible.builtin.systemd:
name: nginx
state: restarted Ansible’s idempotence is task-level, not global-state-level. Running the same playbook twice yields the same result, but Ansible doesn’t track “desired state” across runs like Terraform. This makes it perfect for configuration drift remediation but unsuitable for managing cloud resource lifecycles. For teams adopting GitOps, Ansible playbooks can be triggered by CI pipelines after Terraform completes, bridging the gap between provisioning and configuration.
How do Terraform and Ansible compare across key technical criteria?
Beyond the high-level philosophy, engineering teams need concrete comparisons to make tooling decisions. The following table captures differences observed across dozens of production implementations, including regulated environments requiring audit trails.
| Criteria | Terraform | Ansible |
|---|---|---|
| Primary Model | Declarative (desired state) | Procedural (task sequence) |
| State Management | Explicit state file (local/remote) | No persistent state (idempotent tasks) |
| Execution Target | Cloud/provider APIs | Servers via SSH/WinRM |
| Language | HCL (domain-specific) | YAML + Jinja2 templates |
| Drift Detection | Built-in (terraform plan) | Manual (re-run playbook or use AWX) |
| Rollback Capability | State-based revert (risky) | Re-apply previous playbook version |
| Best For | VPCs, DBs, IAM, K8s clusters | OS config, app deploy, patching |
| Learning Curve | Moderate (state concepts) | Low (procedural familiarity) |
Note that neither tool replaces the other. Teams attempting to force one tool to handle both domains inevitably accumulate technical debt. Terraform’s remote-exec provisioners should be reserved for bootstrap-only tasks (e.g., installing Python for Ansible), not ongoing configuration. Similarly, Ansible’s cloud modules are acceptable for simple, isolated resources but fail at scale due to lack of dependency graphs and efficient state handling.
How do you integrate Terraform and Ansible in a compliant workflow?
Integration is where theory meets production reality. The most resilient pattern uses Terraform outputs to generate Ansible inventory dynamically, eliminating static host files and ensuring configuration always targets current infrastructure. This approach is essential for auto-scaling groups, ephemeral environments, and disaster recovery scenarios where IP addresses change frequently.
Step-by-step integration pattern
- Export Terraform Outputs: Define outputs for instance IPs, DNS names, or tags in your Terraform configuration.
- Generate Dynamic Inventory: Use the
terraform-inventoryscript or write a custom Ansible inventory plugin that parsesterraform output -json. - Pass Secrets Securely: Never embed secrets in Terraform outputs. Use AWS Secrets Manager, HashiCorp Vault, or Ansible Vault, referencing them at runtime.
- Orchestrate in CI/CD: Run
terraform applyfirst, capture outputs, then trigger Ansible playbooks against the generated inventory. - Capture Evidence: Archive Terraform plan JSON and Ansible execution logs to an immutable store (S3 with Object Lock) for audit compliance.
# inventory.tf - Export for Ansible
output "ansible_inventory" {
value = {
webservers = {
hosts = {
for i, ip in aws_instance.web[*].private_ip :
"web-${i}" => { ansible_host = ip }
}
vars = {
environment = "production"
region = var.aws_region
}
}
}
sensitive = false
} This pattern enforces the Ansible vs Terraform: Config vs Provisioning boundary programmatically. Terraform owns the "what exists," Ansible owns the "how it's configured." For teams managing Kubernetes secrets or multi-cluster deployments, this separation extends naturally: Terraform provisions EKS/GKE clusters and node pools; Ansible (or better, ArgoCD/Helm) manages in-cluster resources. Avoid using Ansible to create Kubernetes objects directly—it lacks the reconciliation loop of a proper GitOps controller.
Stop debating Ansible vs Terraform: Config vs Provisioning and start integrating
The Ansible vs Terraform: Config vs Provisioning distinction is not academic—it directly impacts deployment reliability, security posture, and audit readiness. Terraform builds and tracks your infrastructure foundation with declarative precision; Ansible configures and maintains the systems running on that foundation with procedural flexibility. Forcing either tool outside its domain creates fragility that compounds over time.
If your team is struggling with state drift, slow deployments, or failed audits, the issue likely isn’t the tools themselves but how they’re applied. Start by auditing your current automation: move all cloud resource creation to Terraform with remote state, consolidate OS/app configuration into Ansible roles, and wire them together with dynamic inventory. For hands-on guidance tailored to your stack, reach out to discuss your infrastructure automation strategy.