Once Terraform finishes provisioning your servers, how does Ansible know where they are? Here are three clean methods to automate the handoff — from simple file generation to dynamic cloud discovery to remote state parsing.
Terraform and Ansible are built to work together, not compete. Terraform provisions the infrastructure — the virtual machines, networks, and storage — and Ansible steps in once those machines are running to install software, configure services, and deploy your application. The tools do fundamentally different jobs, and in a well-structured pipeline they hand off work to each other seamlessly.
The friction point most people hit is the handoff itself. Once Terraform finishes building your servers, Ansible needs to know where they are — their IP addresses or hostnames. The obvious fix is copying those IPs from your terminal and pasting them into a static Ansible hosts.ini file by hand. It works for a weekend project. In production, it breaks down fast: manual errors creep in, CI/CD pipelines stall, and the moment an instance restarts with a new IP address your inventory is out of date.
The cleaner solution is automating the handoff entirely, so the moment Terraform finishes, Ansible already knows exactly where to connect. Here are the three most practical ways to do that, ordered from simplest to most sophisticated.
Quick Answer: 3 Methods at a Glance
Method 1: Generate the Ansible Inventory Automatically with local_file
The most direct approach is having Terraform write the Ansible inventory file for you. Terraform's local_file resource lets you generate any text file at the end of a terraform apply run — including a correctly formatted hosts.ini that Ansible can read immediately.
The Implementation
Here's a working example for a cluster of AWS EC2 instances.
Note: The local_file resource requires the hashicorp/local provider. Add the following block to your terraform.tf or versions.tf file if it is not already declared:
terraform {
required_providers {
local = {
source = "hashicorp/local"
version = "~> 2.0"
}
}
}
Then your main configuration:
# Provision your compute instances
resource "aws_instance" "web_nodes" {
count = 3
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = {
Name = "web-server-${count.index}"
Role = "webserver"
}
}
# Capture the public IP addresses as an output
output "web_node_ips" {
value = aws_instance.web_nodes[*].public_ip
description = "Public IP addresses of the deployed web nodes."
}
# Write those IPs directly into an Ansible inventory file
resource "local_file" "ansible_inventory" {
content = <<EOT
[webservers]
${join("\n", aws_instance.web_nodes[*].public_ip)}
EOT
filename = "${path.module}/ansible/hosts.ini"
}
How It Works
When terraform apply completes, Terraform calculates the public IPs assigned by AWS, uses the join function to format them one per line, and writes the result to ./ansible/hosts.ini. Your next pipeline step can run Ansible immediately — no intermediate script, no manual copy-paste:
terraform apply -auto-approve
ansible-playbook -i ansible/hosts.ini ansible/site.yml
Honest Verdict
Pros: Simple to set up, no external plugins or dependencies, works out of the box with any Terraform project.
Cons: Generates a static snapshot. If an instance stops and restarts with a new IP, the file is out of date until you re-run Terraform.
Best for: Local staging environments, fast prototyping, and tightly controlled short-lived CI/CD testing pipelines where infrastructure doesn't change between provisioning and deployment.
Method 2: Use Cloud Dynamic Inventory Plugins
When you're running infrastructure at scale — auto-scaling groups, multiple engineers deploying simultaneously, instances spinning up and down — a static text file on a local disk breaks down quickly. The better approach is letting Ansible query your cloud provider directly for the current live state of your infrastructure.
This is what Dynamic Inventory Plugins are for. In this pattern, Terraform's only responsibility is provisioning and tagging infrastructure cleanly. Ansible then reads the live cloud environment, grouping servers automatically based on those tags — no files, no manual steps.
The Implementation
First, tag your Terraform resources consistently so Ansible can find them:
resource "aws_instance" "app_nodes" {
count = 2
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.medium"
tags = {
Environment = "production"
Project = "vortex-momentum"
AnsibleGroup = "appservers"
}
}
Next, create an Ansible dynamic inventory config file named aws_ec2.yml in your Ansible project directory:
---
plugin: aws_ec2
regions:
- us-east-1
filters:
tag:Project: vortex-momentum
instance-state-name: [ 'running' ]
hostnames:
- dns-name
- public-ip-address
keyed_groups:
- key: tags.AnsibleGroup
prefix: group
Running the Pipeline
Make sure boto3 is installed and your AWS credentials are configured, then run:
ansible-playbook -i aws_ec2.yml ansible/deploy.yml
How It Works
When the playbook runs, Ansible calls the AWS API, filters for running instances tagged Project=vortex-momentum, and automatically groups them into internal Ansible groups based on your keyed_groups definition — in this case group_appservers. The inventory is always current because it reflects the live state of your cloud account, not a file generated at a previous point in time.
Honest Verdict
Pros: True production scalability. Handles auto-scaling, variable IP addresses, and multi-developer environments cleanly. No local files to go stale. The live cloud state is the single source of truth.
Cons: Slightly slower startup since Ansible makes an API call before beginning any playbook tasks. Requires correct IAM permissions on whatever machine is running the playbook.
Best for: Production cloud environments, infrastructure using auto-scaling groups, and any setup where multiple people are deploying changes across a shared environment.
Method 3: Read Terraform State Directly from a Remote Backend
There are scenarios where neither local file generation nor cloud tagging works well — particularly in high-security environments where servers live behind jump boxes, public tags aren't used, or compliance requirements restrict how infrastructure metadata is exposed. In these cases, you can configure Ansible to read your Terraform state file directly from a secure remote backend.
This approach skips the cloud API entirely. Ansible connects to your state backend — an S3 bucket, Terraform Cloud, HashiCorp Consul — reads the state JSON, and builds an in-memory inventory from the output values Terraform recorded.
The Implementation
Configure your Terraform backend and outputs explicitly:
terraform {
backend "s3" {
bucket = "my-company-terraform-states"
key = "prod/infrastructure.tfstate"
region = "us-east-1"
}
}
output "database_private_ips" {
value = aws_instance.db_nodes[*].private_ip
}
Then write an Ansible playbook that reads the state before configuring any servers:
---
- name: "Bootstrap inventory from remote Terraform state"
hosts: localhost
gather_facts: false
tasks:
- name: "Fetch state file from S3"
amazon.aws.aws_s3:
bucket: "my-company-terraform-states"
object: "prod/infrastructure.tfstate"
mode: getstr
register: tf_state_string
- name: "Parse state and build in-memory inventory"
vars:
state_json: "{{ tf_state_string.contents | from_json }}"
db_ips: "{{ state_json.outputs.database_private_ips.value }}"
add_host:
name: "{{ item }}"
groups: dynamic_db_group
loop: "{{ db_ips }}"
- name: "Configure database servers"
hosts: dynamic_db_group
become: true
tasks:
- name: "Ensure PostgreSQL is installed"
ansible.builtin.apt:
name: postgresql
state: present
How It Works
The playbook runs in two distinct phases. Phase one runs entirely on your local control node — it pulls the raw state JSON from S3, parses the database_private_ips output, and uses add_host to build an in-memory inventory group called dynamic_db_group. Phase two immediately targets that group to complete the configuration. Nothing is written to disk, and nothing is exposed via public cloud tags.
Honest Verdict
Pros: The most secure option. Reads directly from your source-of-truth state file without public tagging or local file exposure. Works well in air-gapped or highly restricted network environments.
Cons: Requires solid understanding of Ansible filters and state file structure. Demands tightly controlled access permissions over your backend storage. More moving parts to debug when something goes wrong.
Best for: Financial services, healthcare, or other compliance-heavy environments with strict data handling requirements. Also useful for private infrastructure that isn't tagged or queryable through a cloud API.
Choosing the Right Method
The right method depends on where you are in the infrastructure lifecycle:
Starting out or building a prototype? Method 1 — the local_file approach gets you running in an afternoon with no additional setup.
Running production cloud infrastructure? Method 2 — dynamic inventory plugins are the standard approach for this environment. The extra setup pays off immediately in reliability.
Operating in a regulated or high-security environment? Method 3 — reading remote state directly gives you the control and auditability compliance requirements typically demand.
Most real teams eventually end up using Method 2 for most of their infrastructure, with Method 3 reserved for specific sensitive workloads like databases or secrets management systems where tagging metadata publicly isn't appropriate.
Official Documentation References
These are the primary sources for each tool used in this guide:
HashiCorp local_file resource: developer.hashicorp.com/terraform/language/resources/provisioners/local-exec — official reference for the local provider and file generation
Ansible AWS EC2 dynamic inventory plugin: docs.ansible.com/ansible/latest/collections/amazon/aws/aws_ec2_inventory.html — full configuration options for the plugin used in Method 2
Ansible add_host module: docs.ansible.com/ansible/latest/collections/ansible/builtin/add_host_module.html — reference for the in-memory inventory technique used in Method 3
Terraform S3 backend configuration: developer.hashicorp.com/terraform/language/settings/backends/s3 — full options for the remote state backend used in Method 3
What to Read Next
If you're building out a full DevOps or cloud engineering skillset, the Terraform/Ansible integration pattern is one piece of a wider toolkit Terraform vs Ansible for Beginners: What They Actually Do and Which to Learn First. Our covers the foundational difference between provisioning and configuration management if you want the conceptual layer before diving into pipeline integration patterns.
About the Author
Jakpa Desmond Igho is a remote infrastructure analyst and workspace optimization writer. Over the past five years, he has followed workspace hardware trends and reliability discussions across the tech sector. Find more breakdowns at VortexMomentum.tech.

Comments
Post a Comment