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.

See also: AI DevOps Ansible Community on Skool

When to Use Each

| Task | Terraform | Ansible | |------|-----------|---------| | Create VMs, networks, LBs | ✅ Primary | Possible but not ideal | | Install packages, configure OS | Possible 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))

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

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

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 }

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

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 }}")

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

Terraform for provisioning, Ansible for configuration — Don't fight each tool's strengths Terraform outputs → Ansible inventory — Use outputs to dynamically generate host lists Separate state per environment — Different Terraform workspaces or state files for dev/staging/prod Avoid Terraform provisioners — Prefer running Ansible separately after terraform apply for better error handling Pin provider versions — Both Terraform providers and Ansible collections Use the cloud.terraform collection — Native Ansible-Terraform integration CI/CD orchestration — Pipeline runs Terraform first, then Ansible, with proper error handling 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.

Related Articles

Ansible vs Terraform ComparisonAnsible AWS Complete GuideAnsible GitOps with AAP

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home