~/blog/ansible-vs-terraform-2026
zsh
DEVOPS

Ansible vs Terraform 2026: When We Use Each (and When We Use Both)

Engineering Team 2026-03-19

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:

PhaseWhat HappensBest Tool
Day 0 — ProvisioningCreate cloud resources, networks, clustersTerraform
Day 1 — ConfigurationInstall software, configure OS, deploy appsAnsible
Day 2 — OperationsPatching, updates, compliance checks, rotationAnsible
Lifecycle — ChangesAdd/remove/modify infrastructureTerraform
Lifecycle — DriftDetect and fix configuration driftBoth

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-exec provisioner 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

DimensionTerraformAnsible
Primary useInfrastructure provisioningConfiguration management
ApproachDeclarative (desired state)Procedural (ordered tasks)
LanguageHCLYAML
StateState file (tracks resources)Stateless (queries live systems)
AgentNone (API-based)None (SSH-based)
Cloud support4,000+ providersCloud modules available but limited
OS configurationMinimal (provisioners)Excellent (2,000+ modules)
Learning curveMedium (HCL, state management)Low (YAML, SSH)
IdempotencyBuilt-in (state comparison)Module-dependent (most are idempotent)
RollbackDestroy and recreateManual (no built-in rollback)
Secret managementState file exposure riskAnsible Vault built-in
Drift detectionterraform planCustom checks required
Best forCloud resources lifecycleServer config, app deployment
LicenseBSL 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:

  1. Create an ansible/ directory alongside your terraform/ directory
  2. Set up dynamic inventory for your cloud provider
  3. Start with a simple playbook that installs monitoring agents on all servers
  4. Expand to application deployment and security hardening

Adding Terraform to an Ansible project:

  1. Identify cloud resources that Ansible currently creates (EC2, VPCs, S3)
  2. Import them into Terraform state with terraform import
  3. Remove the Ansible tasks that provision cloud resources
  4. 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.

Talk to our infrastructure automation consultants →

Continue exploring these related topics

$ suggest --service

Need Terraform expertise?

We help teams build scalable, maintainable infrastructure-as-code with Terraform.

Get started
Chat with real humans
Chat on WhatsApp