One-page summary — important extracts & flow
VpcCIDR — VPC IP range (default 10.0.0.0/16)
Subnet1CIDR — Subnet 1 range (default 10.0.1.0/24) — AZ A
Subnet2CIDR — Subnet 2 range (default 10.0.2.0/24) — AZ B
ImageUrl — ECR image URI to run in ECS (e.g. 123456789012.dkr.ecr.<region>.amazonaws.com/documentportal:latest)
ECR repository
MyECRRepository (AWS::ECR::Repository)
Stores container images (documentportal). ScanOnPush: true, ImageTagMutability: MUTABLE.
Network
MyVPC (AWS::EC2::VPC) — CidrBlock: !Ref VpcCIDR
Subnet1 (AWS::EC2::Subnet) — CidrBlock: !Ref Subnet1CIDR, AZ = first AZ, MapPublicIpOnLaunch: true
Subnet2 (AWS::EC2::Subnet) — CidrBlock: !Ref Subnet2CIDR, AZ = second AZ, MapPublicIpOnLaunch: true
InternetGateway (AWS::EC2::InternetGateway)
AttachGateway (AWS::EC2::VPCGatewayAttachment) — attaches IGW to MyVPC
RouteTable (AWS::EC2::RouteTable) — for MyVPC
PublicRoute (AWS::EC2::Route) — 0.0.0.0/0 → InternetGateway (DependsOn AttachGateway)
RouteAssoc1 & RouteAssoc2 — associate route table to Subnet1 and Subnet2
Result: both subnets are public and have internet routing.
ECS core
ECSCluster (AWS::ECS::Cluster) — document-portal-cluster
IAM / Roles
ECSExecutionRole (AWS::IAM::Role) — ecsTaskExecutionRole
AssumeRole for ecs-tasks.amazonaws.com
Inline policy grants:
CloudWatch Logs: CreateLogGroup, CreateLogStream, PutLogEvents (Resource "*")
Secrets Manager: GetSecretValue (Resource "*")
Note: currently broad — recommend using managed policy AmazonECSTaskExecutionRolePolicy + scoped secret/log ARNs.
Security
ECSSecurityGroup (AWS::EC2::SecurityGroup)
In MyVPC
Ingress: tcp 8080 from 0.0.0.0/0 (public)
Task Definition
ECSTaskDefinition (AWS::ECS::TaskDefinition)
Family: documentportaltd
CPU: 1024 (1 vCPU), Memory: 8192 (8GB)
NetworkMode: awsvpc, RequiresCompatibilities: FARGATE
ExecutionRoleArn: !GetAtt ECSExecutionRole.Arn
Container:
Image: !Ref ImageUrl
PortMappings: ContainerPort 8080
Environment: LANGCHAIN_PROJECT=DOCUMENT PORTAL
Secrets: multiple ValueFrom secret ARNs (hardcoded in us-east-1)
LogConfiguration: awslogs → group /ecs/documentportal, region = stack region, stream prefix ecs
Service
ECSService (AWS::ECS::Service)
DependsOn: AttachGateway
LaunchType: FARGATE
DesiredCount: 1
NetworkConfiguration (awsvpc):
AssignPublicIp: ENABLED
Subnets: Subnet1, Subnet2
SecurityGroups: ECSSecurityGroup
TaskDefinition: !Ref ECSTaskDefinition
Outputs
ECSClusterName → cluster logical name
TaskDefinitionArn → task definition ARN
On stack create: VPC & subnets -> IGW & routes -> ECR repo -> IAM role -> ECS cluster -> task definition -> ECS service.
Service launches Fargate task(s). Because awsvpc + AssignPublicIp: ENABLED, each task gets an ENI and a public IP in either Subnet1 or Subnet2.
Tasks pull the container image from the ImageUrl. Execution role must permit image pull (ECR) + secrets read + logs write.
At task start, ECS fetches Secrets Manager secrets (from the ARNs) and injects them as environment variables. Container logs go to CloudWatch Logs under /ecs/documentportal.
Public exposure
AssignPublicIp: ENABLED + SG allowing 0.0.0.0/0:8080 → tasks publicly reachable.
Recommendation: use an ALB in public subnets and set AssignPublicIp: DISABLED, keep tasks in private subnets. ALB handles TLS and public traffic.
Broad IAM permissions
Execution role uses wildcard Resource: "*". Replace with:
managed policy AmazonECSTaskExecutionRolePolicy (for ECR + logs) and
a scoped inline policy for specific Secrets Manager ARNs (and KMS Decrypt if needed).
Secrets region mismatch
Secrets ARNs point to us-east-1. If stack runs in another region, either:
move/mirror secrets to same region or
allow cross-region secret access (more complexity). Best: keep secrets in same region.
No Task Role
No TaskRoleArn present. If your app must call AWS services (S3, DynamoDB, SQS), create a separate Task Role with least privileges and attach via TaskRoleArn in TaskDefinition.
Scaling & HA
DesiredCount: 1 — single task. For high availability use >=2 across AZs or define ECS Service Auto Scaling (Application Auto Scaling).
Logging & retention
Consider creating CloudWatch LogGroup in template with RetentionInDays and scope logs permissions to that ARN.
Put tasks in private subnets. Use ALB in public subnets → ALB front-end, tasks private.
Change AssignPublicIp to DISABLED.
Create ALB + TargetGroup (port 8080), register ECS Service with ALB.
Replace wildcard IAM policies with:
ManagedPolicyArns: AmazonECSTaskExecutionRolePolicy
Scoped secretsmanager:GetSecretValue to specific secret ARNs
Add ecr:* and kms:Decrypt as needed (scoped)
Add TaskRole for runtime AWS API access (not the execution role).
Increase DesiredCount and add AutoScaling policies (scale on CPU/memory or custom metrics).
Configure health checks and healthCheckGracePeriodSeconds.
Parameterize secret ARNs or use Mappings to handle regions/accounts.
Create CloudWatch LogGroup resource with retention and scope logs permissions.
Harden security group: only allow ALB SG → Task SG, remove 0.0.0.0/0 unless necessary.
Public subnets: ALB (internet-facing)
Private subnets: ECS tasks (no public IP)
IGW + Route Tables: public subnets route to IGW; private subnets route to NAT (if outbound internet needed)
Security groups:
ALB SG: allow 80/443 from 0.0.0.0/0
Task SG: allow port 8080 from ALB SG only
ImageUrl — correct ECR URI and region
Secrets ARNs — region & account match your cluster
CPU/Memory combo validity for Fargate
AssignPublicIp — do you need public IP per task?
Execution role policies and missing TaskRoleArn