Using Terraform with Ansible: The Complete Integration Guide

Comprehensive guide to integrating Terraform and Ansible for end-to-end infrastructure automation, including patterns, workflows, and best practices for 2026

💡
Last Reviewed for Accuracy by Ryan Fee on June 12, 2025.

Introduction

In the contemporary IT landscape, the ability to rapidly provision, configure, and manage infrastructure is paramount. The dual pressures of accelerating development cycles and maintaining stable, secure environments have driven widespread adoption of Infrastructure as Code (IaC) and Configuration as Code (CaC) principles.

HashiCorp Terraform and Ansible (now part of Red Hat) have emerged as leading tools enabling these practices. While each possesses distinct strengths, their combined use offers a powerful paradigm for comprehensive, end-to-end automation of the entire infrastructure lifecycle.

Terraform excels at infrastructure provisioning and orchestration, allowing organizations to define and manage infrastructure resources across cloud providers and on-premises environments through declarative configuration files. Ansible, by contrast, is a robust automation engine focused on configuration management, application deployment, and task automation within provisioned environments.

This guide explores how to leverage these two powerful tools together, examining integration patterns, practical use cases, best practices, and how to incorporate them into CI/CD pipelines for maximum effectiveness in 2026 and beyond.

Terraform vs. Ansible: Understanding When to Use Each

Core Differences

The most fundamental distinction between Terraform and Ansible lies in their primary focus:

Terraform is predominantly an orchestration tool. Its core strength is provisioning and managing the lifecycle of infrastructure resources—creating, updating, and destroying virtual machines, networks, storage, and DNS entries across various cloud providers and on-premises systems. Terraform excels at defining the "what"—the desired state of infrastructure components and their interdependencies.

Ansible is primarily a configuration management tool. Its forte is automating the setup and maintenance of software and systems within provisioned infrastructure. This includes installing packages, configuring services, deploying applications, and ensuring systems adhere to specific configurations. Ansible excels at defining the "how"—the steps to bring a system to its desired configured state.

While some overlap exists (Terraform can perform basic configuration via provisioners, and Ansible can provision infrastructure via cloud modules), their architectures and design principles are optimized for these distinct roles.

Philosophical Differences: Declarative vs. Procedural

Terraform employs a declarative approach. Users define the desired end state of infrastructure in HCL (HashiCorp Configuration Language). Terraform then analyzes this desired state against the current actual state (tracked in its state file) and determines the necessary actions (create, update, delete) to achieve it. The order of resource definitions is generally not significant, as Terraform builds a dependency graph to determine execution sequence.

Ansible utilizes a procedural (imperative) approach. Ansible Playbooks, written in YAML, consist of tasks executed in the order they are defined. Users specify explicit steps to reach desired configuration, providing direct control over execution flow.

State Management

Terraform is stateful. It maintains a state file (terraform.tfstate) that stores a representation of managed infrastructure, mapping resources defined in configuration to real-world objects. This state file is crucial for planning changes, tracking dependencies, and managing resource lifecycles.

Ansible is largely stateless. It does not maintain persistent records of configuration state between runs. Each playbook execution assesses current node state and performs actions to achieve the desired configuration. While Ansible modules aim for idempotency, the tool doesn't rely on stored state like Terraform does.

Comparison Table

FeatureTerraformAnsible
Primary Use CaseInfrastructure Provisioning & OrchestrationConfiguration Management & Deployment
ApproachDeclarativeProcedural/Imperative
LanguageHCLYAML
State ManagementStateful (maintains tfstate file)Largely Stateless
Infrastructure TypeFavors ImmutableOften used with Mutable
Resource LifecycleStrong (create, update, delete)Limited (configuration focus)
Agent RequirementAgentless (APIs)Agentless (SSH/WinRM)
Dependency HandlingBuilds resource graphExecutes tasks in order

Why Use Terraform and Ansible Together?

Complementary Strengths

The core idea behind using Terraform and Ansible together is that they address different, sequential layers of the automation stack:

Terraform handles "Day 0" activities—initial provisioning and lifecycle management of infrastructure components like virtual machines, networks, and load balancers. It answers: "What infrastructure do I need, and where?"

Ansible handles "Day 1 and beyond" tasks—configuration of provisioned resources. This includes installing software, applying security policies, deploying application code, and managing ongoing system states. It answers: "Now that I have this infrastructure, how do I make it do what I need?"

This division of labor leverages the best of both worlds:

  • Terraform's declarative approach and state management ensure reliable, consistent infrastructure provisioning that can be versioned and evolved over time
  • Ansible's procedural nature and extensive module library provide granular control over system configuration and application deployment

End-to-End Automation

By integrating Terraform and Ansible, organizations can automate the entire service lifecycle:

  1. Define Infrastructure - Teams define required resources (servers, networks, databases) using Terraform's HCL
  2. Provision Infrastructure - Terraform provisions resources across cloud providers or on-premises, ensuring correct dependencies
  3. Configure Systems - Ansible playbooks configure provisioned resources, setting up operating systems, installing packages, hardening security
  4. Deploy Applications - Ansible deploys application code, manages configurations, and orchestrates deployment workflows
  5. Ongoing Management - Terraform scales or modifies infrastructure while Ansible applies updates, patches, and configuration changes

Key Benefits

  • End-to-End Automation - From bare cloud resources to fully configured applications
  • Consistency & Reliability - Infrastructure and configuration as code minimize errors
  • Scalability - Easily scale infrastructure with Terraform and configure new resources with Ansible
  • Efficiency - Drastically reduces manual effort and deployment times
  • Better Collaboration - Code-based definitions enable version control and DevOps workflows
  • Faster Disaster Recovery - Terraform can rapidly provision infrastructure while Ansible quickly configures and deploys applications
  • Immutable Infrastructure Support - Ideal for patterns where Ansible bakes configurations into golden images for deployment

Integration Patterns: Connecting Terraform and Ansible

Successfully combining Terraform and Ansible requires a well-defined integration strategy. Several patterns have emerged, each with advantages and typical use cases.

Pattern 1: Terraform Provisioners (local-exec)

How it works: The local-exec provisioner runs a command locally on the machine executing Terraform. It can invoke an Ansible playbook targeting newly created resources, passing IP addresses or other identifiers from Terraform to Ansible.

Example:

resource "aws_instance" "web" {
  ami           = "ami-0c55b31ad2c455b55"
  instance_type = "t2.micro"
  key_name      = "your-ssh-key-pair"

  tags = {
    Name = "WebServer"
  }
}

resource "null_resource" "wait_for_ssh" {
  depends_on = [aws_instance.web]

  provisioner "remote-exec" {
    inline = ["echo 'SSH is up'"]
    connection {
      type        = "ssh"
      user        = "ec2-user"
      private_key = file("~/.ssh/your-ssh-key.pem")
      host        = aws_instance.web.public_ip
    }
  }
}

resource "null_resource" "ansible_provision" {
  depends_on = [null_resource.wait_for_ssh]

  provisioner "local-exec" {
    command = <<EOT
      ansible-playbook \
        -i "${aws_instance.web.public_ip}," \
        --private-key ~/.ssh/your-ssh-key.pem \
        -u ec2-user \
        playbooks/configure-nginx.yml
    EOT
  }
}

Pros:

  • Simple to implement for basic scenarios
  • Configuration occurs immediately after provisioning within same terraform apply workflow

Cons:

  • Tightly couples Terraform and Ansible; Ansible failures can leave Terraform in inconsistent state
  • Increases duration of Terraform runs
  • Requires Ansible on Terraform execution machine with network connectivity to new resources
  • Complex error handling spanning both tools

Pattern 2: Dynamic Inventory from Terraform Output

How it works: Terraform provisions infrastructure and generates outputs (IP addresses, instance IDs, DNS names). Ansible reads these outputs to build its inventory, decoupling the two tools.

Terraform outputs:

output "web_server_ips" {
  value = aws_instance.web[*].public_ip
}

output "web_server_ids" {
  value = aws_instance.web[*].id
}

Ansible inventory file (terraform_inventory.ini):

[web_servers]
web_server_1 ansible_host=<IP_from_terraform>
web_server_2 ansible_host=<IP_from_terraform>

[web_servers:vars]
ansible_user=ec2-user
ansible_ssh_private_key_file=~/.ssh/your-key.pem

Pros:

  • Decouples Terraform and Ansible execution
  • Ansible operates on inventory reflecting current infrastructure state
  • More robust and scalable for complex environments
  • Clearer separation of concerns

Cons:

  • Slight delay between provisioning and configuration if not orchestrated by CI/CD
  • Learning curve for dynamic inventory plugins
  • Requires secure management of Terraform state file access

Pattern 3: Cloud-Specific Dynamic Inventory Plugins

How it works: Ansible uses built-in inventory plugins (aws_ec2, azure_rm, gcp_compute) that query cloud provider APIs to discover resources. Terraform applies specific tags (environment:prod, role:webserver) that the Ansible plugin uses to filter and group hosts.

Ansible inventory plugin example (aws_inventory.yml):

plugin: aws_ec2
regions:
  - us-east-1
keyed_groups:
  - key: 'tags.Environment'
    prefix: env
  - key: 'tags.Role'
    prefix: role
filters:
  tag:Provisioner: terraform
hostnames:
  - ip-address

Terraform (applying tags):

resource "aws_instance" "web" {
  ami           = "ami-0c55b31ad2c455b55"
  instance_type = "t2.micro"

  tags = {
    Name        = "WebServer"
    Environment = "production"
    Role        = "webserver"
    Provisioner = "terraform"
  }
}

Pros:

  • Most flexible and scalable approach
  • Live discovery from cloud provider APIs
  • Excellent for dynamic, auto-scaling environments
  • Minimal coupling between tools

Cons:

  • Requires cloud API credentials for Ansible
  • Most complex setup initially
  • Plugin-specific syntax and configuration

Using Ansible Provisioner in Terraform

The Terraform Ansible provisioner provides direct integration, allowing Ansible to run immediately after resource creation.

Basic Usage

resource "aws_instance" "example" {
  ami           = "ami-0c55b31ad2c455b55"
  instance_type = "t2.micro"

  provisioner "ansible" {
    plays {
      playbook {
        file_path = "${path.module}/playbook.yml"
      }
    }

    on_failure = continue  # or fail
  }

  depends_on = [aws_instance.example]
}

With Dynamic Inventory

resource "null_resource" "configure_web_servers" {
  provisioner "local-exec" {
    command = "ansible-playbook -i ansible/inventory.ini playbooks/web_setup.yml"
  }

  depends_on = [aws_instance.web]
}

Passing Variables from Terraform to Ansible

resource "null_resource" "run_playbook" {
  provisioner "local-exec" {
    command = "ansible-playbook -i inventory.ini playbooks/app_deploy.yml -e 'app_version=${var.app_version} environment=${var.environment}'"
  }
}

Dynamic Inventory from Terraform State

Reading Terraform State Directly

Ansible can read Terraform state files to build inventory, though this requires careful access management.

Using terraform_state plugin:

---
plugin: community.general.terraform_state
hostnames:
  - private_ip
groups:
  tag_Name: tags.Name

Accessing remote state:

For remote backends (Terraform Cloud, S3), ensure Ansible has appropriate credentials and access controls.

Generating Inventory from Terraform Outputs

A more secure approach uses Terraform to generate inventory files:

locals {
  ansible_inventory = {
    all = {
      children = {
        web_servers = {
          hosts = {
            for instance in aws_instance.web :
            instance.tags["Name"] => {
              ansible_host = instance.public_ip
              ansible_user = "ec2-user"
            }
          }
        }
      }
    }
  }
}

resource "local_file" "inventory" {
  content  = yamlencode(local.ansible_inventory)
  filename = "${path.module}/inventory.yml"
}

Shared Variables and Outputs

Passing Configuration Between Tools

Terraform outputs for Ansible:

output "database_endpoint" {
  value       = aws_db_instance.main.endpoint
  description = "Database endpoint for Ansible configuration"
}

output "app_servers" {
  value = {
    for instance in aws_instance.app :
    instance.tags["Name"] => instance.private_ip
  }
}

Ansible reading outputs (via local script):

#!/bin/bash
TERRAFORM_OUTPUTS=$(terraform output -json)
DB_ENDPOINT=$(echo $TERRAFORM_OUTPUTS | jq -r '.database_endpoint.value')

Terraform reading Ansible facts:

resource "null_resource" "gather_facts" {
  provisioner "local-exec" {
    command = "ansible-playbook playbooks/gather_facts.yml --extra-vars 'output_file=/tmp/facts.json'"
  }
}

locals {
  ansible_facts = jsondecode(file("/tmp/facts.json"))
}

Workflow Orchestration

Sequential Execution with CI/CD

GitLab CI/CD example:

stages:
  - provision
  - configure
  - test

provision_infrastructure:
  stage: provision
  script:
    - terraform init
    - terraform plan -out=tfplan
    - terraform apply tfplan
    - terraform output -json > terraform_outputs.json
  artifacts:
    paths:
      - terraform_outputs.json
      - .terraform

configure_with_ansible:
  stage: configure
  dependencies:
    - provision_infrastructure
  script:
    - ansible-playbook -i inventory.ini playbooks/app_setup.yml
  only:
    - main

smoke_tests:
  stage: test
  script:
    - ansible-playbook playbooks/smoke_tests.yml

GitHub Actions example:

name: Infrastructure and Configuration

on: [push]

jobs:
  provision:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: hashicorp/setup-terraform@v1
      - run: terraform init
      - run: terraform apply -auto-approve
      - run: terraform output -json > outputs.json
      - uses: actions/upload-artifact@v2
        with:
          name: terraform-outputs
          path: outputs.json

  configure:
    needs: provision
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/download-artifact@v2
        with:
          name: terraform-outputs
      - run: pip install ansible
      - run: ansible-playbook -i inventory.ini playbooks/setup.yml

Real-World Architecture Patterns

Multi-Environment Architecture

infrastructure/
├── terraform/
│   ├── main.tf
│   ├── variables.tf
│   ├── outputs.tf
│   └── environments/
│       ├── dev/
│       ├── staging/
│       └── production/
└── ansible/
    ├── inventory/
    │   ├── dev.ini
    │   ├── staging.ini
    │   └── production.ini
    ├── playbooks/
    │   ├── common.yml
    │   ├── app_deploy.yml
    │   └── monitoring_setup.yml
    └── roles/
        ├── web_server/
        ├── database/
        └── monitoring/

GitOps Workflow

  1. Developer commits infrastructure code (Terraform) and configuration code (Ansible) to Git
  2. CI/CD Pipeline validates Terraform and Ansible syntax
  3. Terraform runs in plan mode, showing infrastructure changes
  4. Manual Approval gates production deployments
  5. Terraform applies infrastructure changes
  6. Ansible runs automatically to configure infrastructure
  7. Monitoring validates configuration and application health

Auto-Scaling Pattern

# Terraform manages infrastructure
resource "aws_autoscaling_group" "app" {
  min_size         = 2
  max_size         = 10
  desired_capacity = 4

  launch_template {
    id      = aws_launch_template.app.id
    version = "$Latest"
  }
}

# User data triggers configuration
resource "aws_launch_template" "app" {
  user_data = base64encode(<<EOF
#!/bin/bash
# Wait for instance to be ready
sleep 30
# Configure with Ansible (golden image approach preferred)
# or trigger dynamic inventory update
EOF
  )
}

Best Practices for 2026

1. Use Dynamic Inventory Over Provisioners

Preferred approach:

  • Let Terraform provision infrastructure
  • Use cloud-native dynamic inventory plugins in Ansible
  • Decouple the tools for better maintainability

Avoid:

  • Tight coupling via provisioners for complex configurations
  • Mixing orchestration and configuration in single tool

2. Implement Proper State Management

# Store Terraform state securely
terraform {
  backend "s3" {
    bucket         = "terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"
  }
}

3. Tag Infrastructure for Orchestration

locals {
  common_tags = {
    Environment = var.environment
    ManagedBy   = "terraform"
    Provisioner = "scalr"  # or your management platform
    Team        = var.team
    CostCenter  = var.cost_center
  }
}

4. Use Configuration as Code

# Ansible playbooks should be idempotent
---
- name: Configure web servers
  hosts: web_servers
  gather_facts: yes

  roles:
    - common
    - web_server
    - monitoring

  handlers:
    - name: restart nginx
      service:
        name: nginx
        state: restarted

5. Implement Comprehensive Testing

# Test playbook syntax
ansible-playbook playbooks/site.yml --syntax-check

# Dry run before applying
ansible-playbook playbooks/site.yml --check

# Validate with ansible-lint
ansible-lint playbooks/

# Test with Terraform
terraform validate
terraform plan -detailed-exitcode

6. Secure Sensitive Data

# Use Terraform Cloud/Enterprise for secrets
variable "database_password" {
  sensitive = true
  type      = string
}
# Use Ansible Vault for sensitive playbook data
ansible-vault create group_vars/databases/vault.yml
ansible-playbook playbooks/site.yml --ask-vault-pass

7. Monitor Orchestration Operations

  • Log Terraform applies and Ansible playbook runs
  • Monitor for configuration drift
  • Track changes in version control
  • Alert on failed deployments
  • Document infrastructure changes

8. Plan for Disaster Recovery

# Keep Terraform state and Ansible playbooks in sync
# Document recovery procedures
# Test recovery regularly
# Maintain backups of state files

Operational Challenges and Solutions

State Management at Scale

Challenge: Securely storing and managing Terraform state files across teams

Solution:

  • Use remote state backends (Terraform Cloud, S3 with encryption, Consul)
  • Implement state locking to prevent concurrent modifications
  • Use role-based access control for state file access
  • Maintain state file backups

Inventory Synchronization

Challenge: Keeping Ansible inventory synchronized with Terraform-provisioned resources

Solution:

  • Use dynamic inventory plugins that query cloud APIs
  • Implement automated inventory refresh in CI/CD pipelines
  • Use tagging strategies for inventory grouping
  • Monitor inventory for drift

Secrets Management

Challenge: Handling sensitive data (API keys, passwords, credentials) securely

Solution:

  • Use dedicated secrets management tools (HashiCorp Vault, AWS Secrets Manager)
  • Never commit secrets to version control
  • Rotate credentials regularly
  • Audit secret access

Cross-Team Collaboration

Challenge: Enabling multiple teams to work with Terraform and Ansible safely

Solution:

  • Use management platforms like Scalr for centralized governance
  • Implement role-based access control (RBAC)
  • Enforce policies through Policy as Code (OPA, Sentinel)
  • Maintain clear documentation and runbooks

Conclusion

The combination of Terraform and Ansible provides a powerful approach to infrastructure and configuration automation. Terraform handles the "what" of infrastructure, while Ansible handles the "how" of configuration management. Used together with proper integration patterns, they enable organizations to achieve:

  • Complete end-to-end automation from bare resources to fully deployed applications
  • Consistent, reliable infrastructure managed through code
  • Faster deployment cycles and improved disaster recovery
  • Better collaboration through Infrastructure and Configuration as Code practices
  • Scalable automation that grows with organizational needs

The key to success is choosing the right integration pattern for your specific needs—favoring loose coupling through dynamic inventory over tight coupling through provisioners, maintaining clear separation of concerns, implementing proper state and secrets management, and using higher-level orchestration platforms when operational complexity demands it.

As infrastructure automation continues to evolve, the Terraform-Ansible combination remains a foundational approach for organizations of all sizes seeking to automate their infrastructure lifecycle effectively.

For more information on orchestration alternatives and management platforms, see our guide on Ansible Tower and Automation Controller Alternatives.