# Foundation Layer - VPC and Core Infrastructure (Free Tier Optimized) # Creates base networking infrastructure with minimal cost for learning/development # Data source for availability zones data "aws_availability_zones" "available" { state = "available" } # VPC resource "aws_vpc" "main" { cidr_block = var.vpc_cidr enable_dns_hostnames = true enable_dns_support = true tags = { Name = "${var.project_name}-vpc" Environment = var.environment Project = var.project_name } } # Internet Gateway resource "aws_internet_gateway" "main" { vpc_id = aws_vpc.main.id tags = { Name = "${var.project_name}-igw" Environment = var.environment Project = var.project_name } } # Public Subnets (using 2 AZs for cost optimization) resource "aws_subnet" "public" { count = 2 vpc_id = aws_vpc.main.id cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index) availability_zone = data.aws_availability_zones.available.names[count.index] map_public_ip_on_launch = true tags = { Name = "${var.project_name}-public-subnet-${count.index + 1}" Environment = var.environment Project = var.project_name Type = "public" } } # Private Subnets (created but will use public for now to avoid NAT Gateway costs) # These can be activated later when you want to upgrade to production-ready setup resource "aws_subnet" "private" { count = var.enable_private_subnets ? 2 : 0 vpc_id = aws_vpc.main.id cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index + 10) availability_zone = data.aws_availability_zones.available.names[count.index] tags = { Name = "${var.project_name}-private-subnet-${count.index + 1}" Environment = var.environment Project = var.project_name Type = "private" } } # Conditional NAT Gateway resources (only if private subnets are enabled) resource "aws_eip" "nat" { count = var.enable_private_subnets && var.enable_nat_gateway ? (var.single_nat_gateway ? 1 : 2) : 0 domain = "vpc" depends_on = [aws_internet_gateway.main] tags = { Name = "${var.project_name}-nat-eip-${count.index + 1}" Environment = var.environment Project = var.project_name } } resource "aws_nat_gateway" "main" { count = var.enable_private_subnets && var.enable_nat_gateway ? (var.single_nat_gateway ? 1 : 2) : 0 allocation_id = aws_eip.nat[count.index].id subnet_id = aws_subnet.public[count.index].id depends_on = [aws_internet_gateway.main] tags = { Name = "${var.project_name}-nat-gw-${count.index + 1}" Environment = var.environment Project = var.project_name } } # Route Table for Public Subnets 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" Environment = var.environment Project = var.project_name } } # Route Tables for Private Subnets (only if enabled) resource "aws_route_table" "private" { count = var.enable_private_subnets ? 2 : 0 vpc_id = aws_vpc.main.id # Only add route to NAT Gateway if NAT Gateway is enabled dynamic "route" { for_each = var.enable_nat_gateway ? [1] : [] content { cidr_block = "0.0.0.0/0" # If single NAT gateway, all route tables use index 0, otherwise use the route table's index nat_gateway_id = aws_nat_gateway.main[var.single_nat_gateway ? 0 : count.index].id } } tags = { Name = "${var.project_name}-private-rt-${count.index + 1}" Environment = var.environment Project = var.project_name } } # Associate Public Subnets with Public Route Table resource "aws_route_table_association" "public" { count = 2 subnet_id = aws_subnet.public[count.index].id route_table_id = aws_route_table.public.id } # Associate Private Subnets with Private Route Tables (only if enabled) resource "aws_route_table_association" "private" { count = var.enable_private_subnets ? 2 : 0 subnet_id = aws_subnet.private[count.index].id route_table_id = aws_route_table.private[count.index].id } # Default Security Group resource "aws_security_group" "default" { name = "${var.project_name}-default-sg" description = "Default security group for ${var.project_name}" vpc_id = aws_vpc.main.id ingress { from_port = 0 to_port = 0 protocol = "-1" self = true } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } tags = { Name = "${var.project_name}-default-sg" Environment = var.environment Project = var.project_name } } # Security Group for ALB resource "aws_security_group" "alb" { name = "${var.project_name}-alb-sg" description = "Security group for Application Load Balancer" vpc_id = aws_vpc.main.id ingress { from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { from_port = 443 to_port = 443 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"] } tags = { Name = "${var.project_name}-alb-sg" Environment = var.environment Project = var.project_name } } # Security Group for ECS Tasks resource "aws_security_group" "ecs_tasks" { name = "${var.project_name}-ecs-tasks-sg" description = "Security group for ECS tasks" vpc_id = aws_vpc.main.id # Allow traffic from ALB ingress { from_port = 0 to_port = 65535 protocol = "tcp" security_groups = [aws_security_group.alb.id] } # For development: allow direct access (remove in production) ingress { from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { from_port = 8080 to_port = 8080 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"] } tags = { Name = "${var.project_name}-ecs-tasks-sg" Environment = var.environment Project = var.project_name } } # Conditional VPC Endpoints (only if enabled and cost-optimized) resource "aws_vpc_endpoint" "s3" { count = var.enable_vpc_endpoints ? 1 : 0 vpc_id = aws_vpc.main.id service_name = "com.amazonaws.${var.aws_region}.s3" tags = { Name = "${var.project_name}-s3-endpoint" Environment = var.environment Project = var.project_name } } # S3 Bucket for Terraform State resource "aws_s3_bucket" "terraform_state" { bucket = "${var.project_name}-terraform-state-${random_string.bucket_suffix.result}" tags = { Name = "${var.project_name}-terraform-state" Environment = var.environment Project = var.project_name } } # Random string for bucket uniqueness resource "random_string" "bucket_suffix" { length = 8 special = false upper = false } # S3 Bucket Versioning resource "aws_s3_bucket_versioning" "terraform_state" { bucket = aws_s3_bucket.terraform_state.id versioning_configuration { status = "Enabled" } } # S3 Bucket Server Side Encryption resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" { bucket = aws_s3_bucket.terraform_state.id rule { apply_server_side_encryption_by_default { sse_algorithm = "AES256" } } } # S3 Bucket Public Access Block resource "aws_s3_bucket_public_access_block" "terraform_state" { bucket = aws_s3_bucket.terraform_state.id block_public_acls = true block_public_policy = true ignore_public_acls = true restrict_public_buckets = true } # DynamoDB Table for Terraform State Locking resource "aws_dynamodb_table" "terraform_locks" { name = "${var.project_name}-terraform-locks" billing_mode = "PAY_PER_REQUEST" hash_key = "LockID" attribute { name = "LockID" type = "S" } tags = { Name = "${var.project_name}-terraform-locks" Environment = var.environment Project = var.project_name } }