ECS Fargate Cluster with Task Definition and Service
Scenario
You have containerised the web application, built a Docker image, and pushed it to a private ECR repository. Now you want to run it on AWS Fargate without managing any EC2 instances. Fargate is serverless container compute — you define CPU/memory and AWS handles the underlying host.
Create an ECS cluster, a task definition for the container, and a Fargate service that keeps 2 tasks running at all times.
- Create an aws_ecs_cluster with Container Insights enabled.
- Create an execution role (ecs-tasks.amazonaws.com) that allows Fargate to pull images and write logs. Attach the AmazonECSTaskExecutionRolePolicy managed policy.
- Create a task role — a separate IAM role your application code assumes (for calling AWS services). Keep it empty for now.
- Create an aws_ecs_task_definition with Fargate launch type, 256 CPU units, 512 MB memory, and awslogs log driver.
- Create an aws_ecs_service with desired_count = 2 in two private subnets.
Additional Context
Fargate uses two distinct IAM roles. The execution role is used by AWS (the Fargate control plane) to set up the task — pulling the image and writing logs. The task role is assumed by your running container code to call AWS APIs. Confusing them is the most common ECS IAM mistake.
Container definitions are a JSON string inside the task definition resource. Use jsonencode() to avoid raw JSON strings in your configuration.
versions.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
}
}variables.tf
variable "aws_region" { type = string; default = "us-east-1" }
variable "cluster_name" { type = string; default = "web-app-cluster" }
variable "container_image" { type = string; description = "Full ECR image URI, e.g. 123456789012.dkr.ecr.us-east-1.amazonaws.com/web-app-prod:v1.0.0" }
variable "vpc_id" { type = string }
variable "private_subnets" { type = list(string); description = "Subnets for Fargate tasks." }
variable "environment" { type = string; default = "prod" }main.tf
# main.tf
provider "aws" {
region = var.aws_region
}
# ECS Cluster with Container Insights enabled (CloudWatch metrics per task).
resource "aws_ecs_cluster" "main" {
name = var.cluster_name
setting {
name = "containerInsights"
value = "enabled"
}
tags = { Environment = var.environment, ManagedBy = "terraform" }
}
# Trust policy for ECS tasks.
data "aws_iam_policy_document" "ecs_assume" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]
principals { type = "Service"; identifiers = ["ecs-tasks.amazonaws.com"] }
}
}
# Execution role — used by Fargate to pull ECR images and write CloudWatch logs.
resource "aws_iam_role" "execution" {
name = "${var.cluster_name}-execution-role"
assume_role_policy = data.aws_iam_policy_document.ecs_assume.json
}
resource "aws_iam_role_policy_attachment" "execution" {
role = aws_iam_role.execution.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}
# Task role — assumed by the running container. Add permissions here as needed.
resource "aws_iam_role" "task" {
name = "${var.cluster_name}-task-role"
assume_role_policy = data.aws_iam_policy_document.ecs_assume.json
tags = { Environment = var.environment }
}
# CloudWatch Log Group for container logs.
resource "aws_cloudwatch_log_group" "ecs" {
name = "/ecs/${var.cluster_name}"
retention_in_days = 30
}
# Security group for Fargate tasks — allows outbound internet, restrict inbound.
resource "aws_security_group" "tasks" {
name = "ecs-tasks-sg-${var.environment}"
vpc_id = var.vpc_id
egress { from_port = 0; to_port = 0; protocol = "-1"; cidr_blocks = ["0.0.0.0/0"] }
ingress { from_port = 8080; to_port = 8080; protocol = "tcp"; cidr_blocks = ["10.0.0.0/16"] }
tags = { Environment = var.environment }
}
# Task Definition — specifies the container(s) to run and their resource limits.
resource "aws_ecs_task_definition" "web" {
family = "${var.cluster_name}-web"
network_mode = "awsvpc" # required for Fargate
requires_compatibilities = ["FARGATE"]
cpu = "256" # 0.25 vCPU
memory = "512" # 512 MB
execution_role_arn = aws_iam_role.execution.arn
task_role_arn = aws_iam_role.task.arn
# Container definition as JSON — use jsonencode() to avoid escaping issues.
container_definitions = jsonencode([{
name = "web"
image = var.container_image
essential = true
portMappings = [{
containerPort = 8080
protocol = "tcp"
}]
# Log configuration — send all stdout/stderr to CloudWatch.
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = aws_cloudwatch_log_group.ecs.name
"awslogs-region" = var.aws_region
"awslogs-stream-prefix" = "web"
}
}
environment = [
{ name = "APP_ENV"; value = var.environment }
]
}])
}
# ECS Service — keeps the desired number of tasks running.
resource "aws_ecs_service" "web" {
name = "${var.cluster_name}-web-svc"
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.web.arn
desired_count = 2
launch_type = "FARGATE"
network_configuration {
subnets = var.private_subnets
security_groups = [aws_security_group.tasks.id]
assign_public_ip = false
}
# Replace tasks automatically if the task definition changes.
deployment_minimum_healthy_percent = 50
deployment_maximum_percent = 200
}outputs.tf
output "cluster_arn" { value = aws_ecs_cluster.main.arn }
output "service_name" { value = aws_ecs_service.web.name }
output "task_definition_arn" { value = aws_ecs_task_definition.web.arn }Workflow Commands
terraform init
terraform apply \
-var="container_image=123456789012.dkr.ecr.us-east-1.amazonaws.com/web-app-prod:v1.0.0" \
-var="vpc_id=vpc-xxxx" \
-var='private_subnets=["subnet-aaa","subnet-bbb"]'
# List running tasks
aws ecs list-tasks --cluster $(terraform output -raw cluster_arn)
# View container logs
aws logs tail /ecs/web-app-cluster --follow
terraform destroy -var="container_image=..." -var="vpc_id=..." -var='private_subnets=["..."]'Apply complete! Resources: 7 added, 0 changed, 0 destroyed. Outputs: cluster_arn = "arn:aws:ecs:us-east-1:123456789012:cluster/web-app-cluster" service_name = "web-app-cluster-web-svc" task_definition_arn = "arn:aws:ecs:us-east-1:123456789012:task-definition/web-app-cluster-web:1"

