Ash-Docs
Infrastructure as Code (IaC) Masterclass

Bridging Terraform & Ansible on AWS EC2

Stop clicking through the AWS Console and manually installing software. In this lab, we use Terraform to provision a blank Ubuntu server (Infrastructure as Code), and then pass the baton to Ansible to automatically install Nginx and deploy a dynamic web page using Jinja2 templates (Configuration Management).


Directory Structure

devops-demo/
├── devops-class-key.pem 
├── main.tf
├── inventory.ini
├── playbook.yml
└── roles/
    └── webserver/
        ├── tasks/
        │   └── main.yml
        └── templates/
            └── index.html.j2

0 Required Tools & Environment

Terraform

Provisioning Engine

Ansible

Config Management

AWS Account

Cloud Provider

WSL / Ubuntu

For Windows Users

1 Project & Security Key Setup

First, download your `.pem` SSH key from the AWS Console and place it in your project folder. Ensure you lock down its permissions, otherwise Ansible will refuse to connect.

Terminal: Directory Skeleton
mkdir devops-demo && cd devops-demo

# Secure the SSH key (Crucial for Ansible)
chmod 400 devops-class-key.pem

# Create base files
touch main.tf inventory.ini playbook.yml

# Create the Ansible role structure
mkdir -p roles/webserver/tasks
mkdir -p roles/webserver/templates
touch roles/webserver/tasks/main.yml
touch roles/webserver/templates/index.html.j2

2 Provisioning the Server (Terraform)

Open main.tf. This script configures a Security Group allowing HTTP/SSH and spins up the latest Ubuntu 22.04 instance. Notice the output block that will hand us the IP address for Ansible.

main.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "ap-south-1"
}

resource "aws_security_group" "web_sg" {
  name        = "ansible_web_sg"
  description = "Allow SSH and HTTP traffic"

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"] 
  }

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"]

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
  }
}

resource "aws_instance" "web_server" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t2.micro"
  key_name      = "devops-class-key" 

  vpc_security_group_ids = [aws_security_group.web_sg.id]

  tags = {
    Name = "Ansible-Target-Node"
  }
}

output "instance_public_ip" {
  value = aws_instance.web_server.public_ip
}

3 Configuration Management (Ansible)

We will use an Ansible Playbook and Role to install Nginx and push a dynamic HTML file containing live server metrics using Jinja2 facts.

A. The Master Playbook (playbook.yml)

playbook.yml
- name: Configure Web Server from Terraform Target
  hosts: webservers
  gather_facts: true
  vars:
    ansible_ssh_common_args: '-o StrictHostKeyChecking=no'
  roles:
    - webserver

B. The Role Tasks (roles/webserver/tasks/main.yml)

roles/webserver/tasks/main.yml
---
- name: Ensure Nginx is installed
  apt:
    name: nginx
    state: present
    update_cache: yes
  become: true

- name: Ensure Nginx service is running and enabled on boot
  service:
    name: nginx
    state: started
    enabled: yes
  become: true

- name: Deploy the dynamic HTML app using Jinja2 templates
  template:
    src: index.html.j2
    dest: /var/www/html/index.html
    owner: www-data
    group: www-data
    mode: '0644'
  become: true

C. The Jinja2 Template (roles/webserver/templates/index.html.j2)

roles/webserver/templates/index.html.j2
<!DOCTYPE html>
<html lang="en">
<head>
    <title>DevOps Web Server | Ash7 Technologies Hub</title>
    <script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-slate-900 flex items-center justify-center min-h-screen text-slate-200">
    <div class="bg-slate-800 p-10 rounded-2xl border border-slate-700 text-center">
        <h1 class="text-4xl font-extrabold text-emerald-400 mb-4">Hello World!</h1>
        <p class="text-lg text-slate-400 mb-6">Provisioned by Terraform & Configured by Ansible.</p>

        <div class="mt-6 bg-slate-900 rounded-xl p-5 border border-slate-700 text-left">
            <h2 class="text-emerald-400 font-semibold mb-3 border-b border-slate-700 pb-2">Live Instance Details</h2>
            <ul class="text-sm space-y-2 font-mono">
                <li><strong>Hostname:</strong> {{ ansible_hostname }}</li>
                <li><strong>Internal IP:</strong> {{ ansible_default_ipv4.address | default('N/A') }}</li>
                <li><strong>OS Release:</strong> {{ ansible_distribution }} {{ ansible_distribution_version }}</li>
            </ul>
        </div>
    </div>
</body>
</html>

Execution & Verification

1. Spin up the EC2 Instance

Terminal
terraform init
Terminal
terraform apply -auto-approve

Copy the `instance_public_ip` that outputs at the end.

2. Update the Inventory

Paste the IP into your inventory.ini file:

inventory.ini
[webservers]
PASTE_IP_HERE ansible_user=ubuntu ansible_ssh_private_key_file=./devops-class-key.pem

3. Run the Configuration Playbook

Terminal
ansible-playbook -i inventory.ini playbook.yml

Once finished, paste the EC2 IP into your web browser to see your dynamically generated site!

Critical Gotchas & Cleanup

The Windows/WSL Path Issue

If you are running WSL on Windows, do not leave your .pem key on the Windows C:\ drive mount. Windows handles file permissions differently, and Ansible will reject the key as "too open." Always move the key to a Linux directory (like your project folder) and run chmod 400.

Teardown to Avoid Billing

When you finish the lab, ensure you destroy the infrastructure so AWS doesn't bill you for a running EC2 instance.

terraform destroy -auto-approve
Thanvir Assif

Created by Thanvir Assif

Cloud & DevOps Engineer | AWS Certified | Full Stack Developer. Helping professionals and enthusiasts master cloud deployments.

© 2026 Thanvir Assif. All rights reserved.