Quick answer: Terraform builds infrastructure; Ansible configures what runs on it; Jenkins and AWS CodePipeline orchestrate when and how both run. The standard integration is: a pipeline checks out your code, runs terraform plan and apply to create servers, reads Terraform outputs (IP addresses, instance IDs) to generate an Ansible inventory, then runs an Ansible playbook against those hosts β all triggered by a Git commit.
This guide shows each integration with working code: an Ansible playbook that consumes Terraform output, a Jenkins declarative pipeline with plan approval, and a CodePipeline/CodeBuild setup that runs Terraform natively on AWS. You will also see how to decide which tool should own which job, and the mistakes that make these integrations brittle.
Who does what: dividing responsibilities
The three tools overlap just enough to cause confusion, so settle the boundaries before writing code:
| Tool | Best at | Avoid using it for |
|---|---|---|
| Terraform | Provisioning cloud resources: VPCs, instances, databases, IAM, DNS | Installing packages or editing files inside servers |
| Ansible | Configuring operating systems and applications on existing hosts | Creating cloud infrastructure (possible, but no state tracking) |
| Jenkins | Flexible orchestration across any cloud or tool, self-hosted | Storing Terraform state or long-lived credentials on the controller |
| AWS CodePipeline | Native, managed orchestration inside AWS with IAM-based auth | Multi-cloud pipelines (it is AWS-centric) |
The pattern that works: Terraform owns the infrastructure layer, Ansible owns the configuration layer, and exactly one orchestrator owns the workflow. For a reminder of how Terraform resources and outputs work, see Creating and Managing Infrastructure Resources in Terraform.
Integrating Terraform with Ansible
The handoff between the two tools is Terraform’s output. Expose the attributes Ansible needs β public IPs, hostnames, SSH users β and Ansible reads them with terraform output -json.
# outputs.tf
output "web_public_ips" {
description = "Public IPs of web servers for Ansible"
value = aws_instance.web[*].public_ip
}
output "ssh_user" {
value = "ubuntu"
}
A small shell step turns the outputs into an inventory file, and a playbook configures the hosts:
# generate-inventory.sh (run after terraform apply)
# terraform output -json web_public_ips | jq -r '.[]' \
# | sed 's/^/web /' | awk '{print $2}' > hosts.ini
# site.yml
- name: Configure web servers created by Terraform
hosts: all
become: true
remote_user: ubuntu
gather_facts: true
tasks:
- name: Install Nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
- name: Deploy index page
ansible.builtin.copy:
content: "Deployed by Terraform + Ansible via Techknowledgehub pipeline\n"
dest: /var/www/html/index.html
mode: "0644"
- name: Ensure Nginx is running
ansible.builtin.service:
name: nginx
state: started
enabled: true
Run it with ansible-playbook -i hosts.ini site.yml. Two refinements make this production-ready:
- Use the dynamic inventory plugin (
amazon.aws.aws_ec2) instead of a generated file. It discovers hosts by tag β for exampleRole = webβ so Terraform only needs to tag resources and no inventory file is written at all. - Add a
wait_for_connectiontask at the start, because instances are usually still booting whenterraform applyreturns.
Avoid the temptation to run Ansible from a Terraform local-exec provisioner. It ties configuration to resource creation, cannot be re-run independently, and HashiCorp documents provisioners as a last resort.
Integrating Terraform with Jenkins
Jenkins runs Terraform as ordinary shell steps inside a declarative pipeline. The important design choices are: run on an agent that has the Terraform binary (a Docker agent is simplest), authenticate with short-lived credentials, save the plan as an artifact, and gate production apply behind a manual approval.
// Jenkinsfile
pipeline {
agent { docker { image 'hashicorp/terraform:1.9' args '--entrypoint=""' } }
environment {
TF_IN_AUTOMATION = '1'
AWS_REGION = 'ap-south-1'
}
stages {
stage('Init & Validate') {
steps {
sh 'terraform init -input=false'
sh 'terraform fmt -check -recursive'
sh 'terraform validate'
}
}
stage('Plan') {
steps {
withCredentials([aws(credentialsId: 'aws-terraform-role')]) {
sh 'terraform plan -input=false -out=tfplan -var-file=envs/prod.tfvars'
sh 'terraform show -no-color tfplan > plan.txt'
}
archiveArtifacts artifacts: 'tfplan, plan.txt'
}
}
stage('Approve') {
when { branch 'main' }
steps {
input message: 'Apply this plan to production?', ok: 'Apply'
}
}
stage('Apply') {
when { branch 'main' }
steps {
withCredentials([aws(credentialsId: 'aws-terraform-role')]) {
sh 'terraform apply -input=false tfplan'
}
}
}
stage('Configure with Ansible') {
when { branch 'main' }
steps {
sh 'ansible-playbook -i inventory/aws_ec2.yml site.yml'
}
}
}
}
Use the Jenkins Credentials plugin (or better, an IAM role attached to the agent) rather than pasting keys into the Jenkinsfile. If multiple jobs can touch the same state, use the Lockable Resources plugin or rely on Terraform’s own state locking with -lock-timeout.
Integrating Terraform with AWS CodePipeline
CodePipeline has no native Terraform action, so the work happens inside a CodeBuild project. The pipeline typically has three stages: Source (CodeCommit, GitHub or Bitbucket), Plan (CodeBuild), a Manual Approval action, and Apply (a second CodeBuild project). The CodeBuild service role is the identity Terraform uses, so no access keys are needed anywhere.
# buildspec-plan.yml
version: 0.2
env:
variables:
TF_IN_AUTOMATION: "1"
TF_VERSION: "1.9.5"
phases:
install:
commands:
- curl -sLo tf.zip "https://releases.hashicorp.com/terraform/${TF_VERSION}/terraform_${TF_VERSION}_linux_amd64.zip"
- unzip -o tf.zip -d /usr/local/bin && terraform version
build:
commands:
- terraform init -input=false
- terraform validate
- terraform plan -input=false -var-file=envs/prod.tfvars -out=tfplan
- terraform show -no-color tfplan > plan.txt
artifacts:
files:
- tfplan
- plan.txt
- "**/*"
The apply buildspec is almost identical but runs terraform apply -input=false tfplan using the artifact from the plan stage. Reviewers read plan.txt from the artifact bucket before clicking Approve. Store state in S3 with locking enabled, and give the CodeBuild role only the permissions the configuration actually needs.
Choosing between Jenkins and CodePipeline
Pick Jenkins when you already run it, need multi-cloud or on-premises targets, or want plugins for everything. Pick CodePipeline when your estate is AWS-only and you want zero servers to maintain and IAM-native authentication. Many Indian enterprises run both β Jenkins for application builds and CodePipeline for infrastructure β which is fine as long as each environment has exactly one path to apply.
Six integration mistakes to avoid
- Letting Ansible create infrastructure too. Two tools managing the same resources means neither has the full picture. Terraform provisions; Ansible configures.
- Running Ansible from Terraform provisioners. Use the pipeline to sequence the tools instead.
- Static inventory files committed to Git. IPs change on every rebuild; use dynamic inventory by tag.
- Long-lived AWS keys in Jenkins credentials. Prefer an instance profile on the agent or OIDC/AssumeRole with short sessions.
- Applying a fresh plan instead of the reviewed artifact. The approver saw one plan; production gets another.
- No lock timeout. Two pipelines racing for the same state fail instantly instead of waiting.
Frequently asked questions
Can Terraform replace Ansible completely?
For immutable infrastructure β baking images with Packer and replacing servers rather than updating them β yes, Ansible becomes optional. For long-lived servers that need patching and configuration changes, Ansible still fills a gap Terraform is not designed for.
Where should the Terraform state live when Jenkins runs it?
In a remote backend such as S3 with locking, never on the Jenkins controller or agent workspace. Agents are ephemeral and workspaces get wiped.
Does CodePipeline support manual approval before apply?
Yes. Add a Manual Approval action between the plan and apply stages; it can notify an SNS topic so reviewers receive an email with a link to the plan artifact.
How do I pass Terraform outputs into Ansible variables?
Run terraform output -json > tf_outputs.json and load it with ansible-playbook -e @tf_outputs.json. Every output becomes an Ansible variable with the same name.
Key takeaways
- Terraform provisions, Ansible configures, and one orchestrator sequences both.
- Hand data between tools using Terraform outputs and Ansible dynamic inventory.
- In Jenkins and CodePipeline, save the plan as an artifact and apply exactly that artifact after approval.
- Use IAM roles or OIDC for authentication; keep state in a locked remote backend.
- Give every environment exactly one path to
apply.
Want to build this complete Terraform, Ansible and Jenkins pipeline hands-on? Our DevOps course covers all three tools plus AWS with live projects, mentor support and placement assistance. For video walkthroughs, subscribe to our YouTube channel.

