Ansible vs Terraform: When to Use Each — The Production Decision Framework
Ansible vs Terraform: when to use each in production.
20+ years shipping production infrastructure and CI/CD at scale. Notes here come from systems that actually shipped.
- ✓Basic understanding of infrastructure concepts (VMs, networks, load balancers). Familiarity with at least one cloud provider (AWS, Azure, GCP).
Use Terraform to provision and manage infrastructure resources (VMs, networks, load balancers). Use Ansible to configure and maintain software on those resources (install packages, deploy apps, manage config files). If you need both, use Terraform to create the infrastructure, then Ansible to configure it.
Think of Terraform as the architect who draws the blueprints and orders the materials to build a house. Ansible is the crew that walks in after the house is framed and installs the plumbing, wiring, and cabinets. The architect doesn't care which brand of faucet goes in — that's the crew's job. The crew can't decide where the walls go — that's the architect's job. Mix them up and you either build a house with no plumbing or try to install cabinets in thin air.
Everyone gets this wrong. They pick Ansible because they already know YAML, or Terraform because HCL looks cool. Then they spend six months fighting the tool instead of shipping. I've seen a team try to manage Kubernetes clusters with Ansible — they ended up with a 2000-line playbook that idempotently failed every third run. I've seen another team use Terraform to install packages on 500 VMs — each apply took 45 minutes and they hit API rate limits daily.
The real problem isn't which tool is better — it's that most engineers don't understand the fundamental difference between provisioning and configuration. Provisioning is about creating and destroying resources. Configuration is about ensuring software runs correctly on existing resources. These are different problems with different failure modes, different state models, and different operational patterns.
By the end of this article, you'll have a decision framework that tells you exactly when to reach for each tool — and when to use both. You'll know the specific production signals that mean you're using the wrong one, and you'll have battle-tested patterns to fix it before it costs you a weekend.
The Core Difference: Declarative vs Procedural — and Why It Matters
Terraform is declarative: you describe the desired end state, and Terraform figures out the steps to get there. Ansible is procedural: you write the steps, and Ansible executes them in order. This isn't just a philosophical difference — it determines which problems each tool can solve.
When you're provisioning infrastructure, you need Terraform's ability to reason about dependencies, create resources in parallel, and destroy everything cleanly. Ansible can't do that — it has no concept of resource dependencies. Try to create a VPC and subnet in the same Ansible playbook, and you'll hit race conditions unless you manually order tasks.
When you're configuring software, you need Ansible's idempotent modules that check current state before making changes. Terraform can't do that — its provisioners are brittle, non-idempotent afterthoughts. I've seen Terraform provisioners corrupt SSH configs because they ran on every apply, not just on creation.
The rule: if the resource has an API and a lifecycle (create, read, update, delete), use Terraform. If it's a file, package, service, or config on an existing machine, use Ansible.
State Management: The Hidden Cost of Getting It Wrong
Terraform uses a state file to track real-world resources. This is its superpower — it knows exactly what exists and can plan changes without touching running resources. But it's also its biggest liability. Lose the state file, and Terraform is blind. Corrupt it, and you'll get 'ResourceAlreadyExists' errors on every apply.
Ansible has no central state. It gathers facts from the target machine on every run, then compares them to the desired state. This makes Ansible stateless and resilient — you can run the same playbook against a fresh VM or an existing one, and it works. But it also means Ansible can't detect resources it didn't create. If someone manually deleted a config file, Ansible will recreate it. If someone manually deleted a Terraform-managed S3 bucket, Terraform will try to recreate it — but only if the state file still references it.
The production pattern: store Terraform state in a remote backend (S3 with DynamoDB locking). Never store it locally. For Ansible, use a version-controlled inventory and run ansible-pull from a Git repo — this gives you a pull-based model that scales to thousands of hosts without a central server.
I once saw a team lose their Terraform state file because they stored it in a Git repo that got force-pushed. They spent a weekend importing 200 resources back into state. Don't be that team.
When to Use Both: The Handoff Pattern
The most common production pattern is to use Terraform to provision infrastructure, then Ansible to configure it. But the handoff between them is where things break. You need a clean contract: Terraform outputs the IP addresses or DNS names, and Ansible picks them up from a dynamic inventory.
Here's the pattern that works: Terraform creates the resources and tags them. Ansible uses those tags to discover hosts via the aws_ec2 inventory plugin. No hardcoded IPs, no manual inventory files. This scales from 1 to 10,000 instances without changes.
I've seen teams try to pass IPs via Terraform outputs to Ansible using shell scripts. It works for a demo, but in production, the script breaks when a host is replaced mid-deploy. Use dynamic inventory — it's built for this.
Another pattern: use Terraform to create an ASG with a launch template that runs ansible-pull on boot. The instance pulls the latest playbook from Git and applies it. This gives you immutable infrastructure with configuration management — the best of both worlds.
When Ansible Wins: Configuration Drift and Compliance
Configuration drift is the silent killer of production environments. A developer SSHes into a server to debug an issue, changes a config file, and forgets to revert. Three months later, that server behaves differently from the rest. Ansible's idempotent modules detect and correct drift on every run. Run it on a cron, and drift never accumulates.
Terraform can't do this. Terraform's state file only tracks resources it created. If someone modifies a file on the server, Terraform doesn't know. The only way to detect drift with Terraform is to run a plan and look for changes — but if the resource wasn't created by Terraform, it won't show up.
Compliance is another Ansible win. Need to ensure every server has SELinux enabled, auditd running, and certain packages removed? Ansible can enforce that in a single playbook. Terraform would need to recreate the entire server to change an OS-level setting — which is insane.
The pattern: use Ansible as a compliance enforcement tool. Run it every hour via cron or a CI pipeline. If a server drifts, Ansible fixes it and alerts. This is how you maintain a secure, consistent fleet without manual audits.
When Terraform Wins: Multi-Cloud and Resource Lifecycle
Terraform's provider model is its killer feature. One tool to manage AWS, Azure, GCP, Kubernetes, and 100+ other providers with the same HCL syntax. Ansible can do multi-cloud too, but it's not native — you need separate modules for each cloud, and they don't share a common resource model.
Resource lifecycle is another Terraform strength. Need to create a VPC, then a subnet, then a route table, then associate them? Terraform handles the dependency graph automatically. Ansible would require you to order tasks manually and add wait loops for resource creation.
I've seen teams use Ansible to create AWS resources by calling the AWS CLI via shell module. It works until a network timeout causes a partial creation — then you have orphaned resources. Terraform's state tracking prevents that. If a resource creation fails, Terraform knows what was created and can clean up.
The pattern: use Terraform for everything that has an API and a lifecycle. This includes cloud resources, DNS records, load balancers, database instances, and Kubernetes objects (via the Kubernetes provider). Reserve Ansible for OS-level configuration and application deployment.
The Gray Zone: Where Both Tools Fail
There are scenarios where neither tool is a good fit. Database schema migrations, for example. Terraform can create the RDS instance, but it can't run ALTER TABLE statements. Ansible can run SQL via the mysql_db module, but it's not designed for complex migration workflows. Use a dedicated migration tool like Flyway or Liquibase.
Another gray zone: application deployment. Ansible can deploy code via git module or synchronize, but it's not a CI/CD tool. Terraform shouldn't be used for deployment at all — it's too slow and state-heavy. Use a proper deployment pipeline (ArgoCD, Spinnaker, or even a simple bash script in CI).
I once worked with a team that used Terraform to deploy application code by creating a new AMI on every deploy. Each deploy took 30 minutes, and they hit the AMI limit within a month. They should have used Ansible to deploy code to existing instances, or better, used a container orchestration platform.
The rule: if the task involves data (database state, application state), neither tool is ideal. If the task involves code deployment, use a CI/CD tool. If the task involves infrastructure or configuration, use Terraform or Ansible respectively.
Performance and Scale: When Each Tool Chokes
Ansible's default configuration is tuned for a dozen servers. Run it against 500 hosts with default forks=5, and a simple playbook takes 20 minutes. The fix: increase forks to 50-100, or use ansible-pull for a pull-based model that scales horizontally.
Terraform's performance bottleneck is the state file. With hundreds of resources, a plan can take minutes. The fix: use workspaces or separate state files for independent components. Also, use -target to plan only specific resources during development.
I've seen a Terraform state file with 10,000 resources take 15 minutes to plan. The team split it into 5 state files by microservice, and plans dropped to under a minute. The lesson: don't put everything in one state file. Use a modular architecture.
For Ansible, the biggest performance killer is fact gathering. If you don't need facts, set gather_facts: no. Also, use the pipelining feature to reduce SSH connections. These two changes can cut playbook runtime by 70%.
Security: Secrets Management and Least Privilege
Both tools need credentials to do their job. Terraform needs cloud provider API keys. Ansible needs SSH keys or WinRM credentials. How you store and use these credentials determines your security posture.
Terraform supports multiple backends for state, but the credentials are usually in environment variables or a provider config file. Never hardcode them. Use a secrets manager like HashiCorp Vault or AWS Secrets Manager. For Ansible, use ansible-vault to encrypt sensitive variables, and store the vault password in a secure CI/CD variable.
I've seen a team commit AWS access keys to a public GitHub repo because they were in a Terraform variables file. The keys were compromised within hours. Use environment variables or a secrets manager — never commit secrets.
Another pattern: use IAM roles for EC2 instances instead of SSH keys. Ansible can connect via SSM (AWS Systems Manager) without exposing SSH ports. This is more secure and eliminates SSH key management.
When Not to Use Either: Simpler Alternatives
Sometimes you don't need either tool. For a single server running a simple app, a bash script with a cron job is faster and easier. For a small team with a handful of instances, manual SSH and a wiki page might be fine.
Terraform is overkill if you have a static infrastructure that changes once a year. Ansible is overkill if you have two servers and you're the only admin. The tools have a learning curve and operational overhead — don't pay that cost if you don't need to.
I've seen a startup spend two months learning Terraform to manage a single EC2 instance. They could have written a 10-line bash script in an hour. The tool should match the scale of the problem.
The rule: if you have fewer than 5 servers and no plans to grow, use bash scripts. If you have 5-50 servers, Ansible is a good fit. If you have 50+ servers or multi-cloud, use both Terraform and Ansible. If you have 500+ servers, consider a pull-based model with ansible-pull and a CI pipeline.
The 4GB Container That Kept Dying
- When memory boundaries are tight, ensure one tool manages the entire stack from OS to app — don't split responsibility across tools without a clear handoff contract.
terraform refreshterraform plan| File | Command / Code | Purpose |
|---|---|---|
| terraform_vs_ansible_example.tf | resource "aws_instance" "web" { | The Core Difference: Declarative vs Procedural |
| terraform_backend.tf | terraform { | State Management |
| ansible_dynamic_inventory.yml | plugin: aws_ec2 | When to Use Both |
| ansible_compliance.yml | - name: Enforce security compliance | When Ansible Wins |
| terraform_multi_cloud.tf | provider "aws" { | When Terraform Wins |
| ansible.cfg | [defaults] | Performance and Scale |
| ansible_vault.yml | db_password: !vault | | Security |
Key takeaways
Common mistakes to avoid
3 patternsUsing Terraform provisioners for configuration
Managing the same resources with both tools
Not pinning tool versions in CI/CD
Interview Questions on This Topic
How does Terraform handle dependency resolution between resources, and what happens if there's a circular dependency?
Frequently Asked Questions
20+ years shipping production infrastructure and CI/CD at scale. Notes here come from systems that actually shipped.
That's Ansible. Mark it forged?
7 min read · try the examples if you haven't