How do you design Ansible and Terraform to work together — what does each tool handle best?
Quick Answer
Terraform provisions infrastructure (VPCs, EC2 instances, RDS databases, EKS clusters) — the 'what exists.' Ansible configures that infrastructure (installing packages, hardening OS, deploying applications) — the 'how it is set up.' Terraform creates the server; Ansible makes it useful.
Detailed Answer
Think of building a house. Terraform is the general contractor who builds the structure — foundation, walls, roof, plumbing, and electrical. Ansible is the interior designer who makes it livable — painting walls, installing appliances, setting up the home network, and arranging furniture. You would never ask the interior designer to pour concrete, and you would not ask the general contractor to pick curtain colors. Each tool excels in its domain, and trying to force one to do the other's job creates fragile, unmaintainable automation.
Terraform excels at declarative infrastructure provisioning through cloud provider APIs. It manages the lifecycle of resources — creating, updating, and destroying VPCs, subnets, security groups, EC2 instances, RDS databases, EKS clusters, S3 buckets, and IAM roles. Terraform's state file tracks what exists and what needs to change, enabling plan-before-apply workflows that show exactly what will be created, modified, or destroyed. For banking infrastructure, Terraform manages the network topology (hub-and-spoke VPC with transit gateway), the compute layer (EKS clusters, EC2 bastion hosts), the data layer (RDS PostgreSQL for settlements-db, ElastiCache for session management), and the security layer (KMS keys, WAF rules, Security Hub integration). Terraform is idempotent through its state — running apply twice produces no changes if the infrastructure matches the desired state.
Ansible excels at procedural configuration management through SSH or WinRM. It manages what happens inside servers — installing packages, configuring services, managing files, hardening operating systems, and deploying applications to non-containerized targets. For banking, Ansible handles CIS benchmark hardening on EC2 instances, installing and configuring monitoring agents (Datadog, Splunk forwarder), managing SSL certificates on legacy application servers, configuring LDAP authentication, and deploying Java applications to Tomcat servers that have not yet been containerized. Ansible is idempotent through its modules — each module checks the current state and only makes changes if needed.
The integration pattern has two approaches: Terraform-first and dynamic inventory. In the Terraform-first approach, Terraform provisions the infrastructure and outputs the IP addresses, hostnames, and connection details. These outputs feed into Ansible's inventory — either through a static inventory file generated by Terraform's local_file resource, or through Ansible's AWS dynamic inventory plugin that discovers EC2 instances by tags. The workflow is: Terraform apply creates servers with specific tags, then Ansible runs with the aws_ec2 dynamic inventory plugin filtering by those tags, configuring each server according to its role. In a banking pipeline, this might look like: Terraform creates three EC2 instances tagged role=settlements-processor, and Ansible's playbook installs Java 17, deploys the settlements application JAR, configures the JVM parameters, sets up the Splunk forwarder, and applies CIS hardening — all based on the role tag.
In production at a bank, the separation becomes clear when you consider change frequency and blast radius. Terraform changes are infrequent and high-impact — modifying a VPC peering connection or upgrading an RDS instance affects the entire platform. These changes go through Terraform Enterprise with Sentinel policies, approval gates, and change windows. Ansible changes are more frequent and targeted — updating an application configuration, rotating a certificate, or patching a package on 50 servers. These changes run through Ansible Automation Platform (AWX/Tower) with job templates, RBAC, and audit logging. Both tools store their code in Git, but they have separate repositories, separate CI/CD pipelines, and separate approval workflows because their risk profiles are fundamentally different.
The biggest gotcha is overlapping responsibility. If Terraform creates a security group and Ansible also tries to manage the same security group rules, they fight each other — Terraform's next apply will revert Ansible's changes, and Ansible's next run will override Terraform's state. Define clear boundaries: Terraform manages everything at the cloud API level (resources you see in the AWS console), and Ansible manages everything at the OS level (things you configure via SSH). Another common mistake is using Terraform's provisioner blocks (remote-exec, local-exec) to do configuration management — provisioners run only on resource creation, not on subsequent applies, they have poor error handling, and they mix infrastructure provisioning with configuration in ways that are hard to debug and maintain. Use Terraform to create, Ansible to configure, and keep them in separate pipelines.