AnsiblePilot — Master Ansible Automation

AnsiblePilot is the leading resource for learning Ansible automation, DevOps, and infrastructure as code. Browse over 1,400 tutorials covering Ansible modules, playbooks, roles, collections, and real-world examples. Whether you are a beginner or an experienced engineer, our step-by-step guides help you automate Linux, Windows, cloud, containers, and network infrastructure.

Popular Topics

About Luca Berton

Luca Berton is an Ansible automation expert, author of 8 Ansible books published by Apress and Leanpub including "Ansible for VMware by Examples" and "Ansible for Kubernetes by Example", and creator of the Ansible Pilot YouTube channel. He shares practical automation knowledge through tutorials, books, and video courses to help IT professionals and DevOps engineers master infrastructure automation.

Ansible Terraform Integration: Orchestrate Infrastructure and Configuration Together

By Luca Berton · Published 2024-01-01 · Category: installation

Integrate Ansible with Terraform for end-to-end infrastructure automation. Provision with Terraform, configure with Ansible, using dynamic inventories.

Introduction

Terraform provisions infrastructure. Ansible configures it. Together they cover the full automation lifecycle — from creating cloud resources to installing software and managing configurations. This guide shows how to integrate them effectively for enterprise automation.

> New to Terraform? Start with how to install Terraform on any OS, then come back here to wire it together with Ansible.

See also: AI DevOps Ansible Community on Skool

When to Use Each

TaskTerraformAnsible
Create VMs, networks, LBs✅ PrimaryPossible but not ideal
Install packages, configure OSPossible but limited✅ Primary
Manage Kubernetes clusters✅ Cluster creation✅ App deployment
Database provisioning✅ RDS/Cloud SQL✅ Self-managed DB config
State tracking✅ Built-in state file❌ Stateless
Configuration drift❌ Detects, doesn't fix✅ Enforces desired state

Integration Patterns

Pattern 1: Terraform Provisions → Ansible Configures

Terraform                  Ansible
┌──────────┐              ┌──────────────┐
│ Create   │  Output IPs  │ Configure    │
│ EC2, VPC,│ ──────────→  │ packages,    │
│ SG, ELB  │              │ services,    │
│          │              │ deploy apps  │
└──────────┘              └──────────────┘

Pattern 2: Ansible Orchestrates Terraform

- name: Full stack deployment
  hosts: localhost
  tasks:
    - name: Provision infrastructure
      cloud.terraform.terraform:
        project_path: ./terraform/
        state: present
      register: tf_output

    - name: Add hosts to inventory
      ansible.builtin.add_host:
        name: "{{ item }}"
        groups: webservers
      loop: "{{ tf_output.outputs.instance_ips.value }}"

    - name: Wait for SSH
      ansible.builtin.wait_for:
        host: "{{ item }}"
        port: 22
        timeout: 300
      loop: "{{ tf_output.outputs.instance_ips.value }}"

- name: Configure provisioned servers
  hosts: webservers
  become: true
  roles:
    - common
    - webserver
    - monitoring

Pattern 3: Terraform Dynamic Inventory

#!/usr/bin/env python3
# terraform_inventory.py — dynamic inventory from Terraform state
import json
import subprocess

def get_terraform_state():
    result = subprocess.run(
        ['terraform', 'output', '-json'],
        capture_output=True, text=True,
        cwd='./terraform/'
    )
    return json.loads(result.stdout)

outputs = get_terraform_state()

inventory = {
    'webservers': {
        'hosts': outputs['web_ips']['value'],
        'vars': {
            'ansible_user': 'ubuntu',
            'ansible_ssh_private_key_file': '~/.ssh/deploy.pem'
        }
    },
    'databases': {
        'hosts': outputs['db_ips']['value'],
        'vars': {
            'ansible_user': 'ubuntu',
            'db_endpoint': outputs['rds_endpoint']['value']
        }
    }
}

print(json.dumps(inventory))

Terraform Ansible Module

Install the cloud.terraform collection:

ansible-galaxy collection install cloud.terraform
pip install python-hcl2

Run Terraform from Ansible

- name: Manage Terraform infrastructure
  cloud.terraform.terraform:
    project_path: ./terraform/aws-vpc/
    state: present
    force_init: true
    variables:
      environment: production
      instance_count: 3
      instance_type: t3.medium
  register: tf

- name: Show Terraform outputs
  ansible.builtin.debug:
    msg: "VPC ID: {{ tf.outputs.vpc_id.value }}"

Destroy Infrastructure

- name: Tear down environment
  cloud.terraform.terraform:
    project_path: ./terraform/aws-vpc/
    state: absent

See also: Ansible London Meetup 2024: Automation & DevOps Insights

Terraform with Ansible Provisioner

In main.tf, trigger Ansible after resource creation:

resource "aws_instance" "web" {
  count         = 3
  ami           = "ami-0abcdef1234567890"
  instance_type = "t3.medium"
  key_name      = "deploy-key"

  tags = {
    Name        = "web-${count.index + 1}"
    Environment = "production"
    Role        = "webserver"
  }

  provisioner "local-exec" {
    command = <<-EOT
      ANSIBLE_HOST_KEY_CHECKING=False \
      ansible-playbook -i '${self.public_ip},' \
        --private-key ~/.ssh/deploy.pem \
        -u ubuntu \
        playbooks/configure-web.yml
    EOT
  }
}

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

CI/CD Pipeline

# .github/workflows/deploy.yml
name: Infrastructure Deployment
on:
  push:
    branches: [main]

jobs:
  provision:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: "1.9"

      - name: Terraform Init & Apply
        working-directory: ./terraform
        run: |
          terraform init
          terraform apply -auto-approve
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

      - name: Get outputs
        id: tf
        working-directory: ./terraform
        run: echo "ips=$(terraform output -json instance_ips)" >> $GITHUB_OUTPUT

      - name: Install Ansible
        run: pip install ansible-core boto3

      - name: Configure servers
        run: |
          ansible-playbook playbooks/site.yml \
            -i "${{ steps.tf.outputs.ips }}" \
            --private-key <(echo "${{ secrets.SSH_PRIVATE_KEY }}")

See also: Learn Ansible: Complete Beginner's Guide & Learning Path (2026)

AWS Example: Full Stack

Terraform (terraform/main.tf)

# VPC, Subnets, Security Groups
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.0"

  name = "app-vpc"
  cidr = "10.0.0.0/16"

  azs             = ["us-east-1a", "us-east-1b"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24"]

  enable_nat_gateway = true
}

# EC2 instances
resource "aws_instance" "app" {
  count         = var.instance_count
  ami           = data.aws_ami.ubuntu.id
  instance_type = var.instance_type
  subnet_id     = module.vpc.private_subnets[count.index % 2]
  key_name      = var.key_name

  tags = {
    Name = "app-${count.index + 1}"
    Role = "application"
  }
}

# RDS Database
resource "aws_db_instance" "main" {
  identifier     = "app-db"
  engine         = "postgresql"
  engine_version = "16"
  instance_class = "db.t3.medium"
  # ... additional config
}

output "app_private_ips" {
  value = aws_instance.app[*].private_ip
}

output "db_endpoint" {
  value = aws_db_instance.main.endpoint
}

Ansible Playbook (playbooks/configure-app.yml)

---
- name: Configure application servers
  hosts: app_servers
  become: true
  vars:
    db_endpoint: "{{ hostvars['localhost']['tf_outputs']['db_endpoint']['value'] }}"
  roles:
    - common
    - role: app
      vars:
        database_url: "postgresql://{{ db_user }}:{{ db_pass }}@{{ db_endpoint }}/myapp"
    - monitoring
    - log_shipping

State Management

# Use remote state for team collaboration
# terraform/backend.tf
terraform {
  backend "s3" {
    bucket = "myorg-terraform-state"
    key    = "production/terraform.tfstate"
    region = "us-east-1"
    encrypt = true
    dynamodb_table = "terraform-locks"
  }
}

Best Practices

  1. Terraform for provisioning, Ansible for configuration — Don't fight each tool's strengths
  2. Terraform outputs → Ansible inventory — Use outputs to dynamically generate host lists
  3. Separate state per environment — Different Terraform workspaces or state files for dev/staging/prod
  4. Avoid Terraform provisioners — Prefer running Ansible separately after terraform apply for better error handling
  5. Pin provider versions — Both Terraform providers and Ansible collections
  6. Use the cloud.terraform collection — Native Ansible-Terraform integration
  7. CI/CD orchestration — Pipeline runs Terraform first, then Ansible, with proper error handling
  8. Tag resources consistently — Same tagging schema in Terraform and Ansible for cross-referencing

FAQ

Why not just use Terraform for everything?

Terraform excels at cloud resource lifecycle (create/update/destroy with state tracking). But OS-level configuration — installing packages, managing services, deploying code — is Ansible's strength. Using both gives you the best of each.

Can AAP run Terraform?

Yes — use the cloud.terraform collection in AAP playbooks. Store Terraform code in the same Git project, and AAP handles both provisioning and configuration in a single workflow.

How to handle Terraform state in team environments?

Use remote backends (S3, Azure Blob, GCS) with state locking (DynamoDB for AWS). Never commit terraform.tfstate to Git.

Conclusion

Terraform and Ansible are complementary — not competing — tools. Terraform manages the infrastructure lifecycle with state tracking while Ansible handles configuration management with agentless simplicity. Together they provide complete infrastructure automation from cloud resources to application deployment.

For a production-focused walkthrough, see Luca Berton's guide on Terraform vs Ansible: when to use which.

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home