TerraformAWS
AWS ECS Fargate Service
Complete ECS Fargate service with ALB, task definition, CloudWatch logs, and auto-scaling.
ecsfargatecontainersalbautoscaling
Prerequisites
- •
VPC with public and private subnets - •
Docker image published to ECR or Docker Hub - •
Terraform >= 1.5.0
Template Code
# ─────────────────────────────────────────────────────────────────────────────
# AWS ECS Fargate Service with ALB and Auto-Scaling
# ─────────────────────────────────────────────────────────────────────────────
variable "environment" { default = "production" }
variable "service_name" { default = "my-app" }
variable "image_uri" {}
variable "container_port" { default = 3000 }
variable "cpu" { default = 256 }
variable "memory" { default = 512 }
variable "desired_count" { default = 2 }
variable "vpc_id" {}
variable "public_subnet_ids" { type = list(string) }
variable "private_subnet_ids" { type = list(string) }
# ── ECS Cluster ───────────────────────────────────────────────────────────────
resource "aws_ecs_cluster" "main" {
name = "${var.environment}-cluster"
setting {
name = "containerInsights"
value = "enabled"
}
}
# ── CloudWatch Log Group ──────────────────────────────────────────────────────
resource "aws_cloudwatch_log_group" "app" {
name = "/ecs/${var.environment}/${var.service_name}"
retention_in_days = 14
}
# ── Task Execution Role ───────────────────────────────────────────────────────
resource "aws_iam_role" "task_execution" {
name = "${var.environment}-${var.service_name}-exec-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "ecs-tasks.amazonaws.com" }
Action = "sts:AssumeRole"
}]
})
}
resource "aws_iam_role_policy_attachment" "task_execution" {
role = aws_iam_role.task_execution.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}
# ── Task Definition ────────────────────────────────────────────────────────────
resource "aws_ecs_task_definition" "app" {
family = "${var.environment}-${var.service_name}"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
cpu = var.cpu
memory = var.memory
execution_role_arn = aws_iam_role.task_execution.arn
container_definitions = jsonencode([{
name = var.service_name
image = var.image_uri
portMappings = [{
containerPort = var.container_port
protocol = "tcp"
}]
environment = [
{ name = "NODE_ENV", value = var.environment }
]
logConfiguration = {
logDriver = "awslogs"
options = {
awslogs-group = aws_cloudwatch_log_group.app.name
awslogs-region = "us-east-1"
awslogs-stream-prefix = "ecs"
}
}
healthCheck = {
command = ["CMD-SHELL", "curl -f http://localhost:${var.container_port}/health || exit 1"]
interval = 30
timeout = 5
retries = 3
}
}])
}
# ── Security Groups ───────────────────────────────────────────────────────────
resource "aws_security_group" "alb" {
name = "${var.environment}-alb-sg"
vpc_id = var.vpc_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"] }
}
resource "aws_security_group" "app" {
name = "${var.environment}-app-sg"
vpc_id = var.vpc_id
ingress {
from_port = var.container_port
to_port = var.container_port
protocol = "tcp"
security_groups = [aws_security_group.alb.id]
}
egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] }
}
# ── ALB ───────────────────────────────────────────────────────────────────────
resource "aws_lb" "main" {
name = "${var.environment}-alb"
load_balancer_type = "application"
subnets = var.public_subnet_ids
security_groups = [aws_security_group.alb.id]
}
resource "aws_lb_target_group" "app" {
name = "${var.environment}-${var.service_name}-tg"
port = var.container_port
protocol = "HTTP"
vpc_id = var.vpc_id
target_type = "ip"
health_check {
path = "/health"
healthy_threshold = 2
unhealthy_threshold = 3
interval = 30
}
}
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.main.arn
port = 80
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.app.arn
}
}
# ── ECS Service ───────────────────────────────────────────────────────────────
resource "aws_ecs_service" "app" {
name = var.service_name
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.app.arn
desired_count = var.desired_count
launch_type = "FARGATE"
network_configuration {
subnets = var.private_subnet_ids
security_groups = [aws_security_group.app.id]
assign_public_ip = false
}
load_balancer {
target_group_arn = aws_lb_target_group.app.arn
container_name = var.service_name
container_port = var.container_port
}
depends_on = [aws_lb_listener.http]
}
output "alb_dns_name" { value = aws_lb.main.dns_name }
Usage
terraform init terraform apply -var="image_uri=123456789.dkr.ecr.us-east-1.amazonaws.com/my-app:latest"