This project automates the provisioning and configuration of infrastructure to host a static website using Terraform for infrastructure provisioning and Ansible for instance configuration. It deploys an EC2 instance running Nginx, fetches static files from an S3 bucket, and makes them available over the web.
.
├── ansible/ # Ansible automation
│ ├── bootstrap.yml # One-off setup tasks
│ ├── playbook.yml # Main playbook to install and configure
│ ├── hosts.ini # Inventory file for EC2 host
│ └── roles/
│ ├── install_awscli/ # Role to install AWS CLI
│ └── setup_nginx/ # Role to install and configure Nginx
│
├── bootstrap-backend_S3Dynamo/ # Terraform backend setup (S3 + DynamoDB)
│ └── *.tf # S3 bucket & DynamoDB state locking
│
├── environments/
│ └── dev/ # Environment-specific infrastructure
│ └── *.tf # EC2 instance, security groups, etc.
│
├── modules/ # Reusable Terraform modules
│ ├── ec2_instance/
│ ├── s3_bucket/
│ └── security_group/
│
├── providers.tf # Terraform provider configuration
├── versions.tf # Terraform version and required provider versions
└── README.md # This file
- 🚀 Provision EC2 instance using Terraform
- 🔐 Secure instance using a dedicated security group
- 🌐 Install & configure Nginx via Ansible
- ☁️ Sync static site content from S3
- 🔄 Backend state management using S3 + DynamoDB
- 🔧 Modularized Terraform structure
- 📦 Reusable Ansible roles
- Terraform >= 1.0
- Ansible >= 2.9
- AWS CLI
- AWS credentials configured (
~/.aws/credentials
or environment variables) - SSH access to the EC2 instance (key pair)
Note: Make sure to comment out backend in case the backend is not configured at all yet.
After creation of backend config (S3 + DyynamoDB) only go for backend.tf being included for terraform operations.
cd bootstrap-backend_S3Dynamo and then,
terraform init
terraform apply
cd environments/dev and then,
terraform init
terraform apply
Outputs will include the EC2 public IP
Update the ansible/hosts.ini with your EC2 public IP:
[webserver]
<ec2-ip> ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/<key>.pem
Then run:
cd ansible and then,
ansible-playbook -i hosts.ini playbook.yml
After provisioning the EC2 instance with Terraform, Ansible is used to configure the server as a static web host. Here's what the playbook accomplishes:
Installs prerequisites:
-
Python 3, pip
-
AWS CLI v2 (if not already installed)
-
Nginx web server
Fetches static website content:
-
The awscli Ansible role downloads files from a specified S3 bucket (e.g., s3:////)
-
The files are synced to /var/www/html/ so that Nginx can serve them as a static website
Configures and starts Nginx:
-
Ensures the service is running and enabled on boot
-
Serves the synced content via the default web root
MIT — use freely and modify for your needs.
PRs are welcome! Open issues for enhancements or bugs.
Once deployed, your static site should be accessible at:
http://<your-ec2-public-ip>
Throughout building this static website infrastructure with Terraform and Ansible, the following challenges were encountered and solved:
Cause : Trust relationship was missing or misconfigured. Fix : Updated trust relationship to allow ec2.amazonaws.com as the trusted entity:
{ "Effect": "Allow", "Principal": { "Service": "ec2.amazonaws.com" }, "Action": "sts:AssumeRole" }
Cause : The IAM role had full S3 permissions, but the EC2 instance was not correctly assuming the role (metadata service unreachable). As the role was not attached, also I did modify an existing role to reuse the same just created it's instance profile and modified IAM role for instance using CLI as follows.
Fix:
-
Confirmed role assumption using aws sts get-caller-identity
-
Ensured EC2 was launched with the correct Instance Profile
-
After updating the trust policy, ensured that the instance profile is created and attached properly to the instance.
aws iam create-instance-profile \
--instance-profile-name <name of profile to be chosen>
aws iam add-role-to-instance-profile \
--instance-profile-name <name of instance profile that you chose eatlier> \
--role-name <Role name>
Attach Instance Profile to EC2 Instance : Get your instance ID (or use Terraform outputs), then:
aws ec2 associate-iam-instance-profile \
--instance-id i-xxxxxxxxxxxxxxxxx \
--iam-instance-profile Name=<Role name>
Cause : The unarchive module in Ansible failed because unzip was not installed.
Fix:
- Added an Ansible task to install unzip before using
unarchive
*Cause : * Mixing roles and tasks in the same play caused redundant Nginx installation blocks. Fix:
- Broke setup into proper Ansible roles: install_awscli, setup_nginx.
- Cleaned up tasks and avoided duplication