AWSTemplateFormatVersion: '2010-09-09' Description: | Tool Zoo application service stack. Builds a dedicated ECS Fargate web service with an internet-facing ALB and a stable CloudFormation-managed URL. Parameters: EnvironmentName: Type: String AllowedPattern: ^[a-zA-Z0-9-]+$ Description: Prefix used for naming resources. ServiceName: Type: String AllowedPattern: ^[a-z0-9-]+$ Description: Logical service name used for the ECS service and log group. ContainerImage: Type: String Description: Fully-qualified container image URI to deploy. ContainerPort: Type: Number Default: 8080 MinValue: 1 MaxValue: 65535 Description: Port exposed by the web container. HealthCheckPath: Type: String Default: "/" Description: HTTP path used by the ALB health check. DesiredCount: Type: Number Default: 1 MinValue: 1 MaxValue: 3 Description: Number of running tasks. TaskCpu: Type: Number Default: 512 AllowedValues: - 256 - 512 - 1024 - 2048 - 4096 Description: Fargate task CPU units. TaskMemory: Type: Number Default: 1024 AllowedValues: - 512 - 1024 - 2048 - 3072 - 4096 - 5120 - 6144 - 7168 - 8192 Description: Fargate task memory in MiB. Mappings: SubnetConfig: Vpc: CIDR: 10.52.0.0/16 PublicOne: CIDR: 10.52.0.0/24 PublicTwo: CIDR: 10.52.1.0/24 Resources: Vpc: Type: AWS::EC2::VPC Properties: CidrBlock: !FindInMap [SubnetConfig, Vpc, CIDR] EnableDnsHostnames: true EnableDnsSupport: true Tags: - Key: Name Value: !Sub ${EnvironmentName}-vpc InternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: !Sub ${EnvironmentName}-igw VpcGatewayAttachment: Type: AWS::EC2::VPCGatewayAttachment Properties: InternetGatewayId: !Ref InternetGateway VpcId: !Ref Vpc PublicSubnetOne: Type: AWS::EC2::Subnet Properties: VpcId: !Ref Vpc AvailabilityZone: !Select [0, !GetAZs ''] CidrBlock: !FindInMap [SubnetConfig, PublicOne, CIDR] MapPublicIpOnLaunch: true Tags: - Key: Name Value: !Sub ${EnvironmentName}-public-a PublicSubnetTwo: Type: AWS::EC2::Subnet Properties: VpcId: !Ref Vpc AvailabilityZone: !Select [1, !GetAZs ''] CidrBlock: !FindInMap [SubnetConfig, PublicTwo, CIDR] MapPublicIpOnLaunch: true Tags: - Key: Name Value: !Sub ${EnvironmentName}-public-b PublicRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref Vpc Tags: - Key: Name Value: !Sub ${EnvironmentName}-public-rt PublicDefaultRoute: Type: AWS::EC2::Route DependsOn: VpcGatewayAttachment Properties: RouteTableId: !Ref PublicRouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGateway PublicSubnetOneRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PublicRouteTable SubnetId: !Ref PublicSubnetOne PublicSubnetTwoRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PublicRouteTable SubnetId: !Ref PublicSubnetTwo LoadBalancerSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: !Sub ${EnvironmentName} ALB ingress VpcId: !Ref Vpc SecurityGroupIngress: - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: 0.0.0.0/0 SecurityGroupEgress: - IpProtocol: -1 CidrIp: 0.0.0.0/0 ServiceSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: !Sub ${EnvironmentName} ECS service ingress VpcId: !Ref Vpc SecurityGroupIngress: - IpProtocol: tcp FromPort: !Ref ContainerPort ToPort: !Ref ContainerPort SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup SecurityGroupEgress: - IpProtocol: -1 CidrIp: 0.0.0.0/0 ApplicationLoadBalancer: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: Scheme: internet-facing Type: application SecurityGroups: - !Ref LoadBalancerSecurityGroup Subnets: - !Ref PublicSubnetOne - !Ref PublicSubnetTwo ApplicationTargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: Port: !Ref ContainerPort Protocol: HTTP TargetType: ip VpcId: !Ref Vpc HealthCheckEnabled: true HealthCheckPath: !Ref HealthCheckPath HealthCheckProtocol: HTTP Matcher: HttpCode: 200-399 ApplicationListener: Type: AWS::ElasticLoadBalancingV2::Listener Properties: DefaultActions: - Type: forward TargetGroupArn: !Ref ApplicationTargetGroup LoadBalancerArn: !Ref ApplicationLoadBalancer Port: 80 Protocol: HTTP LogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub /aws/ecs/${EnvironmentName}/${ServiceName} RetentionInDays: 14 Cluster: Type: AWS::ECS::Cluster Properties: ClusterName: !Sub ${EnvironmentName}-cluster TaskExecutionRole: Type: AWS::IAM::Role Properties: RoleName: !Sub ${EnvironmentName}-task-exec-role AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - ecs-tasks.amazonaws.com Action: - sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy TaskRole: Type: AWS::IAM::Role Properties: RoleName: !Sub ${EnvironmentName}-task-role AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - ecs-tasks.amazonaws.com Action: - sts:AssumeRole TaskDefinition: Type: AWS::ECS::TaskDefinition Properties: Family: !Sub ${EnvironmentName}-taskdef Cpu: !Ref TaskCpu Memory: !Ref TaskMemory NetworkMode: awsvpc RequiresCompatibilities: - FARGATE ExecutionRoleArn: !GetAtt TaskExecutionRole.Arn TaskRoleArn: !GetAtt TaskRole.Arn ContainerDefinitions: - Name: web Image: !Ref ContainerImage Essential: true PortMappings: - ContainerPort: !Ref ContainerPort Protocol: tcp Environment: - Name: PORT Value: !Sub '${ContainerPort}' LogConfiguration: LogDriver: awslogs Options: awslogs-group: !Ref LogGroup awslogs-region: !Ref AWS::Region awslogs-stream-prefix: web Service: Type: AWS::ECS::Service DependsOn: - ApplicationListener Properties: ServiceName: !Ref ServiceName Cluster: !Ref Cluster LaunchType: FARGATE DesiredCount: !Ref DesiredCount HealthCheckGracePeriodSeconds: 120 NetworkConfiguration: AwsvpcConfiguration: AssignPublicIp: ENABLED SecurityGroups: - !Ref ServiceSecurityGroup Subnets: - !Ref PublicSubnetOne - !Ref PublicSubnetTwo LoadBalancers: - TargetGroupArn: !Ref ApplicationTargetGroup ContainerName: web ContainerPort: !Ref ContainerPort TaskDefinition: !Ref TaskDefinition Outputs: ServiceUrl: Description: Public URL for the deployed Tool Zoo service Value: !Sub http://${ApplicationLoadBalancer.DNSName} ClusterName: Description: ECS cluster name for the deployed service Value: !Ref Cluster