infra: add Terraform main infrastructure - VPC, subnets, EC2, security groups, IAM

This commit is contained in:
ikechukwu Elizabeth
2026-04-28 03:49:51 +01:00
parent e653650453
commit e783125d32
5 changed files with 323 additions and 0 deletions
+95
View File
@@ -0,0 +1,95 @@
# ──────────────────────────────────────────────
# Data Source — Latest Amazon Linux 2023 AMI
# ──────────────────────────────────────────────
data "aws_ami" "amazon_linux_2023" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-2023.*-x86_64"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
# ──────────────────────────────────────────────
# EC2 Instance
# ──────────────────────────────────────────────
resource "aws_instance" "app" {
ami = data.aws_ami.amazon_linux_2023.id
instance_type = "t3.micro"
subnet_id = aws_subnet.public.id
vpc_security_group_ids = [aws_security_group.nginx.id]
iam_instance_profile = aws_iam_instance_profile.ec2_profile.name
user_data = <<-EOF
#!/bin/bash
set -e
# Update system
dnf update -y
# Install Docker
dnf install -y docker
systemctl enable docker
systemctl start docker
# Wait for Docker to be fully ready
sleep 10
# Add ec2-user to docker group
usermod -aG docker ec2-user
# Ensure SSM Agent is running (pre-installed on AL2023)
systemctl enable amazon-ssm-agent
systemctl start amazon-ssm-agent
# Authenticate to ECR
aws ecr get-login-password --region ${var.aws_region} | \
docker login --username AWS --password-stdin \
${var.aws_account_id}.dkr.ecr.${var.aws_region}.amazonaws.com
# Pull images from ECR
docker pull ${var.aws_account_id}.dkr.ecr.${var.aws_region}.amazonaws.com/task-backend:latest
docker pull ${var.aws_account_id}.dkr.ecr.${var.aws_region}.amazonaws.com/task-frontend:latest
# Run backend
docker run -d \
--name task-backend \
--restart unless-stopped \
-p 3000:3000 \
${var.aws_account_id}.dkr.ecr.${var.aws_region}.amazonaws.com/task-backend:latest
# Wait for backend to be ready
sleep 5
# Run frontend
docker run -d \
--name task-frontend \
--restart unless-stopped \
-p 80:80 \
--link task-backend:backend \
${var.aws_account_id}.dkr.ecr.${var.aws_region}.amazonaws.com/task-frontend:latest
EOF
root_block_device {
volume_size = 20
volume_type = "gp3"
encrypted = true
delete_on_termination = true
}
metadata_options {
http_tokens = "required"
}
tags = {
Name = "${var.project_name}-app-server"
Project = var.project_name
Environment = var.environment
}
}
+52
View File
@@ -0,0 +1,52 @@
# ──────────────────────────────────────────────
# IAM Role — EC2 instance identity
# ──────────────────────────────────────────────
resource "aws_iam_role" "ec2_role" {
name = "${var.project_name}-ec2-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
Service = "ec2.amazonaws.com"
}
Action = "sts:AssumeRole"
}
]
})
tags = {
Name = "${var.project_name}-ec2-role"
Project = var.project_name
Environment = var.environment
}
}
# ──────────────────────────────────────────────
# Policy Attachments
# ──────────────────────────────────────────────
resource "aws_iam_role_policy_attachment" "ssm" {
role = aws_iam_role.ec2_role.name
policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}
resource "aws_iam_role_policy_attachment" "ecr" {
role = aws_iam_role.ec2_role.name
policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
}
# ──────────────────────────────────────────────
# Instance Profile
# ──────────────────────────────────────────────
resource "aws_iam_instance_profile" "ec2_profile" {
name = "${var.project_name}-ec2-profile"
role = aws_iam_role.ec2_role.name
tags = {
Name = "${var.project_name}-ec2-profile"
Project = var.project_name
Environment = var.environment
}
}
+29
View File
@@ -0,0 +1,29 @@
output "vpc_id" {
description = "ID of the VPC"
value = aws_vpc.main.id
}
output "public_subnet_id" {
description = "ID of the public subnet"
value = aws_subnet.public.id
}
output "private_subnet_id" {
description = "ID of the private subnet"
value = aws_subnet.private.id
}
output "ec2_instance_id" {
description = "EC2 instance ID — use this to connect via SSM"
value = aws_instance.app.id
}
output "ec2_public_ip" {
description = "Public IP of the EC2 instance"
value = aws_instance.app.public_ip
}
output "ec2_public_dns" {
description = "Public DNS of the EC2 instance"
value = aws_instance.app.public_dns
}
+69
View File
@@ -0,0 +1,69 @@
# ──────────────────────────────────────────────
# Security Group — Nginx (Public)
# ──────────────────────────────────────────────
resource "aws_security_group" "nginx" {
name = "${var.project_name}-nginx-sg"
description = "Security group for Nginx reverse proxy"
vpc_id = aws_vpc.main.id
ingress {
description = "HTTP from internet"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "HTTPS from internet"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
description = "Allow all outbound"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.project_name}-nginx-sg"
Project = var.project_name
Environment = var.environment
}
}
# ──────────────────────────────────────────────
# Security Group — Backend (Private)
# ──────────────────────────────────────────────
resource "aws_security_group" "backend" {
name = "${var.project_name}-backend-sg"
description = "Security group for backend API - only accepts traffic from Nginx"
vpc_id = aws_vpc.main.id
ingress {
description = "Allow traffic from Nginx only"
from_port = 3000
to_port = 3000
protocol = "tcp"
security_groups = [aws_security_group.nginx.id]
}
egress {
description = "Allow all outbound"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.project_name}-backend-sg"
Project = var.project_name
Environment = var.environment
}
}
+78
View File
@@ -0,0 +1,78 @@
# ──────────────────────────────────────────────
# VPC
# ──────────────────────────────────────────────
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "${var.project_name}-vpc"
Project = var.project_name
Environment = var.environment
}
}
# ──────────────────────────────────────────────
# Subnets
# ──────────────────────────────────────────────
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = var.public_subnet_cidr
availability_zone = "${var.aws_region}a"
map_public_ip_on_launch = true
tags = {
Name = "${var.project_name}-public-subnet"
Project = var.project_name
Environment = var.environment
}
}
resource "aws_subnet" "private" {
vpc_id = aws_vpc.main.id
cidr_block = var.private_subnet_cidr
availability_zone = "${var.aws_region}a"
tags = {
Name = "${var.project_name}-private-subnet"
Project = var.project_name
Environment = var.environment
}
}
# ──────────────────────────────────────────────
# Internet Gateway
# ──────────────────────────────────────────────
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.project_name}-igw"
Project = var.project_name
Environment = var.environment
}
}
# ──────────────────────────────────────────────
# Route Tables
# ──────────────────────────────────────────────
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = {
Name = "${var.project_name}-public-rt"
Project = var.project_name
Environment = var.environment
}
}
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.public.id
route_table_id = aws_route_table.public.id
}