I think more and more people are using containers when creating applications. This time, I would like to code the mechanism that the Dockerfile is uploaded to CodeCommit and then built to automatically update the ECS task.
Use CloudFormation to manage your infrastructure in code. Refer to the code published by awslabs and create it with Cloudformation. I would like to make it with TerraForm someday.
awslabs https://github.com/awslabs/ecs-refarch-continuous-deployment
The architecture to be created this time is as follows.

EC2 or Fargate can be selected when creating a stack. In addition, AWS official docker image (php-sample) is used when creating a stack, so if you want to use another image, upload the Dockerfile to CodeCommit.
(If you want to use Github for Pipline, please refer to the awslabs code.)
The directory structure is as follows. Please upload the 5 yamls in the templates folder to S3 for use.
├── ecs-refarch-continuous-deployment.yaml
├── tempaltes
    ├── vpc.yaml
    ├── load-balancer.yaml
    ├── ecs-cluster.yaml
    ├── service.yaml
    └── deployment-pipline.yaml
First of all, from VPC.
---
AWSTemplateFormatVersion: 2010-09-09
Parameters:
  Name:
    Type: String
  VpcCIDR:
    Type: String
  Subnet1CIDR:
    Type: String
  Subnet2CIDR:
    Type: String
Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCIDR
      Tags:
        - Key: Name
          Value: !Ref Name
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Ref Name
  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC
  Subnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 0, !GetAZs ]
      MapPublicIpOnLaunch: true
      CidrBlock: !Ref Subnet1CIDR
      Tags:
        - Key: Name
          Value: !Sub ${Name} (Public)
  Subnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 1, !GetAZs ]
      MapPublicIpOnLaunch: true
      CidrBlock: !Ref Subnet2CIDR
      Tags:
        - Key: Name
          Value: !Sub ${Name} (Public)
  RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Ref Name
  DefaultRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref RouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
  Subnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref RouteTable
      SubnetId: !Ref Subnet1
  Subnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref RouteTable
      SubnetId: !Ref Subnet2
Outputs:
  Subnets:
    Value: !Join [ ",", [ !Ref Subnet1, !Ref Subnet2 ] ]
  VpcId:
    Value: !Ref VPC
Next is Load Balancer (ALB). There is also an NLB Template, so if you want to see it, please comment!
Parameters:
  LaunchType:
    Type: String
    Default: Fargate
    AllowedValues:
      - Fargate
      - EC2
  Subnets:
    Type: List<AWS::EC2::Subnet::Id>
  VpcId:
    Type: String
Conditions:
  EC2: !Equals [ !Ref LaunchType, "EC2" ]
Resources:
  SecurityGroup:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      GroupDescription: !Sub ${AWS::StackName}-alb
      SecurityGroupIngress:
        - CidrIp: "0.0.0.0/0"
          IpProtocol: tcp
          FromPort: 80
          ToPort: 80
      VpcId: !Ref VpcId
  LoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Subnets: !Ref Subnets
      SecurityGroups:
        - !Ref SecurityGroup
  LoadBalancerListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      LoadBalancerArn: !Ref LoadBalancer
      Port: 80
      Protocol: HTTP
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref TargetGroup
  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    DependsOn: LoadBalancer
    Properties:
      VpcId: !Ref VpcId
      Port: 80
      Protocol: HTTP
      Matcher:
        HttpCode: 200-299
      HealthCheckIntervalSeconds: 10
      HealthCheckPath: /
      HealthCheckProtocol: HTTP
      HealthCheckTimeoutSeconds: 5
      HealthyThresholdCount: 2
      TargetType: !If [ EC2, "instance", "ip" ]
      TargetGroupAttributes:
        - Key: deregistration_delay.timeout_seconds
          Value: 30
  ListenerRule:
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties:
      ListenerArn: !Ref LoadBalancerListener
      Priority: 1
      Conditions:
        - Field: path-pattern
          Values:
            - /
      Actions:
        - TargetGroupArn: !Ref TargetGroup
          Type: forward
Outputs:
  TargetGroup:
    Value: !Ref TargetGroup
  ServiceUrl:
    Description: URL of the load balancer for the sample service.
    Value: !Sub http://${LoadBalancer.DNSName}
  SecurityGroup:
    Value: !Ref SecurityGroup
Next is Cluseter. This time the AMI used mapping. You could use SSM to get the latest AMI, but I didn't use it because I wanted to automatically select by region.
Parameters:
  LaunchType:
    Type: String
    Default: Fargate
    AllowedValues:
      - Fargate
      - EC2
  InstanceType:
    Type: String
    Default: t2.micro
  ClusterSize:
    Type: Number
    Default: 2
  Subnets:
    Type: List<AWS::EC2::Subnet::Id>
  SecurityGroup:
    Type: AWS::EC2::SecurityGroup::Id
  VpcId:
    Type: AWS::EC2::VPC::Id
  KeyName:
    Description: The EC2 Key Pair to allow SSH access to the instance
    Type: "AWS::EC2::KeyPair::KeyName"
Conditions:
  EC2: !Equals [ !Ref LaunchType, "EC2" ]
Mappings:
  AWSRegionToAMI:
    ap-south-1:
      AMI: ami-00491f6f
    eu-west-3:
      AMI: ami-9aef59e7
    eu-west-2:
      AMI: ami-67cbd003
    eu-west-1:
      AMI: ami-1d46df64
    ap-northeast-2:
      AMI: ami-c212b2ac
    ap-northeast-1:
      AMI: ami-872c4ae1
    sa-east-1:
      AMI: ami-af521fc3
    ca-central-1:
      AMI: ami-435bde27
    ap-southeast-1:
      AMI: ami-910d72ed
    ap-southeast-2:
      AMI: ami-58bb443a
    eu-central-1:
      AMI: ami-509a053f
    us-east-1:
      AMI: ami-28456852
    us-east-2:
      AMI: ami-ce1c36ab
    us-west-1:
      AMI: ami-74262414
    us-west-2:
      AMI: ami-decc7fa6
Resources:
  EC2Role:
    Type: AWS::IAM::Role
    Condition: EC2
    Properties:
      Path: /
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role
  InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Condition: EC2
    Properties:
      Path: /
      Roles:
        - !Ref EC2Role
  Cluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: !Ref AWS::StackName
  AutoScalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    Condition: EC2
    Properties:
      VPCZoneIdentifier: !Ref Subnets
      LaunchConfigurationName: !Ref LaunchConfiguration
      MinSize: !Ref ClusterSize
      MaxSize: !Ref ClusterSize
      DesiredCapacity: !Ref ClusterSize
      Tags: 
        - Key: Name
          Value: !Sub ${AWS::StackName} - ECS Host
          PropagateAtLaunch: true
    CreationPolicy:
      ResourceSignal:
        Timeout: PT15M
    UpdatePolicy:
      AutoScalingRollingUpdate:
        MinInstancesInService: 1
        MaxBatchSize: 1
        PauseTime: PT15M
        WaitOnResourceSignals: true
  LaunchConfiguration:
    Type: AWS::AutoScaling::LaunchConfiguration
    Condition: EC2
    Metadata:
      AWS::CloudFormation::Init:
        config:
          commands:
            01_add_instance_to_cluster:
                command: !Sub echo ECS_CLUSTER=${Cluster} > /etc/ecs/ecs.config
          files:
            "/etc/cfn/cfn-hup.conf":
              mode: 000400
              owner: root
              group: root
              content: !Sub |
                [main]
                stack=${AWS::StackId}
                region=${AWS::Region}
            "/etc/cfn/hooks.d/cfn-auto-reloader.conf":
              content: !Sub |
                [cfn-auto-reloader-hook]
                triggers=post.update
                path=Resources.ContainerInstances.Metadata.AWS::CloudFormation::Init
                action=/opt/aws/bin/cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource LaunchConfiguration
          services:
            sysvinit:
              cfn-hup:
                enabled: true
                ensureRunning: true
                files:
                  - /etc/cfn/cfn-hup.conf
                  - /etc/cfn/hooks.d/cfn-auto-reloader.conf
    Properties:
      ImageId: !FindInMap [ AWSRegionToAMI, !Ref "AWS::Region", AMI ]
      InstanceType: !Ref InstanceType
      IamInstanceProfile: !Ref InstanceProfile
      SecurityGroups:
        - !Ref SecurityGroup
      KeyName: !Ref KeyName
      UserData:
        "Fn::Base64": !Sub |
          #!/bin/bash
          yum install -y aws-cfn-bootstrap
          /opt/aws/bin/cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource LaunchConfiguration
          /opt/aws/bin/cfn-signal -e $? --region ${AWS::Region} --stack ${AWS::StackName} --resource AutoScalingGroup
Outputs:
  ClusterName:
      Value: !Ref Cluster
Next is Service.
Parameters:
  Cluster:
    Type: String
  DesiredCount:
    Type: Number
    Default: 1
  LaunchType:
    Type: String
    Default: Fargate
    AllowedValues:
      - Fargate
      - EC2
  TargetGroup:
    Type: String
  SecurityGroup:
    Type: AWS::EC2::SecurityGroup::Id
  Subnets:
    Type: List<AWS::EC2::Subnet::Id>
Conditions:
  Fargate: !Equals [ !Ref LaunchType, "Fargate" ]
  EC2: !Equals [ !Ref LaunchType, "EC2" ]
Resources:
  TaskExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      Path: /
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
  LogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub /ecs/${AWS::StackName}
      RetentionInDays: 14
  FargateService:
    Type: AWS::ECS::Service
    Condition: Fargate
    Properties:
      Cluster: !Ref Cluster
      DesiredCount: !Ref DesiredCount
      TaskDefinition: !Ref TaskDefinition
      LaunchType: FARGATE
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: ENABLED
          SecurityGroups:
            - !Ref SecurityGroup
          Subnets: !Ref Subnets
      LoadBalancers:
        - ContainerName: simple-app
          ContainerPort: 80
          TargetGroupArn: !Ref TargetGroup
  EC2Service:
    Type: AWS::ECS::Service
    Condition: EC2
    Properties:
      Cluster: !Ref Cluster
      DesiredCount: !Ref DesiredCount
      TaskDefinition: !Ref TaskDefinition
      LaunchType: EC2
      LoadBalancers:
        - ContainerName: simple-app
          ContainerPort: 80
          TargetGroupArn: !Ref TargetGroup
  TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Family: !Sub ${AWS::StackName}-simple-app
      RequiresCompatibilities:
        - !If [ Fargate, "FARGATE", "EC2" ]
      Memory: 512
      Cpu: 256
      NetworkMode: !If [ Fargate, "awsvpc", "bridge" ]
      ExecutionRoleArn: !Ref TaskExecutionRole
      ContainerDefinitions:
        - Name: simple-app
          Image: amazon/amazon-ecs-sample
          Essential: true
          Memory: 256
          MountPoints:
            - SourceVolume: my-vol
              ContainerPath: /var/www/my-vol
          PortMappings:
            - HostPort: 80
              ContainerPort: 80
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-region: !Ref AWS::Region
              awslogs-group: !Ref LogGroup
              awslogs-stream-prefix: !Ref AWS::StackName
        - Name: busybox
          Image: busybox
          EntryPoint:
            - sh
            - -c
          Essential: true
          Memory: 256
          VolumesFrom:
            - SourceContainer: simple-app
          Command:
            - /bin/sh -c "while true; do /bin/date > /var/www/my-vol/date; sleep 1; done"
      Volumes:
        - Name: my-vol
Outputs:
  Service:
    Value: !If [ Fargate, !Ref FargateService, !Ref EC2Service ]
Next is the construction of the pipeline.
Parameters:
  CodeCommitRepositoryName:
    Type: String
  Cluster:
    Type: String
  Service:
    Type: String
Resources:
  Repository:
    Type: AWS::ECR::Repository
    DeletionPolicy: Retain
  CodeBuildServiceRole:
    Type: AWS::IAM::Role
    Properties:
      Path: /
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: codebuild.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: root
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Resource: "*"
                Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                  - ecr:GetAuthorizationToken
                  - secretsmanager:GetSecretValue
              - Resource: !Sub arn:aws:s3:::${ArtifactBucket}/*
                Effect: Allow
                Action:
                  - s3:GetObject
                  - s3:PutObject
                  - s3:GetObjectVersion
              - Resource: !Sub arn:aws:ecr:${AWS::Region}:${AWS::AccountId}:repository/${Repository}
                Effect: Allow
                Action:
                  - ecr:GetDownloadUrlForLayer
                  - ecr:BatchGetImage
                  - ecr:BatchCheckLayerAvailability
                  - ecr:PutImage
                  - ecr:InitiateLayerUpload
                  - ecr:UploadLayerPart
                  - ecr:CompleteLayerUpload
  CodePipelineServiceRole:
    Type: AWS::IAM::Role
    Properties:
      Path: /
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: codepipeline.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: root
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Resource:
                  - !Sub arn:aws:s3:::${ArtifactBucket}/*
                Effect: Allow
                Action:
                  - s3:PutObject
                  - s3:GetObject
                  - s3:GetObjectVersion
                  - s3:GetBucketVersioning
              - Resource: "*"
                Effect: Allow
                Action:
                  - ecs:DescribeServices
                  - ecs:DescribeTaskDefinition
                  - ecs:DescribeTasks
                  - ecs:ListTasks
                  - ecs:RegisterTaskDefinition
                  - ecs:UpdateService
                  - codebuild:StartBuild
                  - codebuild:BatchGetBuilds
                  - iam:PassRole
  ArtifactBucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Retain
  CodeBuildProject:
    Type: AWS::CodeBuild::Project
    Properties:
      Artifacts:
        Type: CODEPIPELINE
      Source:
        Type: CODEPIPELINE
        BuildSpec: |
          version: 0.2
          phases:
            pre_build:
              commands:
                - $(aws ecr get-login --no-include-email)
                - TAG="$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | head -c 8)"
                - IMAGE_URI="${REPOSITORY_URI}:${TAG}"
            build:
              commands:
                - docker build --tag "$IMAGE_URI" .
            post_build:
              commands:
                - docker push "$IMAGE_URI"
                - printf '[{"name":"simple-app","imageUri":"%s"}]' "$IMAGE_URI" > images.json
          artifacts:
            files: images.json
      Environment:
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/docker:17.09.0
        Type: LINUX_CONTAINER
        EnvironmentVariables:
          - Name: AWS_DEFAULT_REGION
            Value: !Ref AWS::Region
          - Name: REPOSITORY_URI
            Value: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${Repository}
      Name: !Ref AWS::StackName
      ServiceRole: !Ref CodeBuildServiceRole
  Pipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      RoleArn: !GetAtt CodePipelineServiceRole.Arn
      ArtifactStore:
        Type: S3
        Location: !Ref ArtifactBucket
      Stages:
        - Name: Source
          Actions:
            - Name: App
              ActionTypeId:
                Category: Source
                Owner: AWS
                Version: 1
                Provider: CodeCommit
              Configuration:
                RepositoryName: !Ref CodeCommitRepositoryName
                BranchName: master
              RunOrder: 1
              OutputArtifacts:
                - Name: App
        - Name: Build
          Actions:
            - Name: Build
              ActionTypeId:
                Category: Build
                Owner: AWS
                Version: 1
                Provider: CodeBuild
              Configuration:
                ProjectName: !Ref CodeBuildProject
              InputArtifacts:
                - Name: App
              OutputArtifacts:
                - Name: BuildOutput
              RunOrder: 1
        - Name: Deploy
          Actions:
            - Name: Deploy
              ActionTypeId:
                Category: Deploy
                Owner: AWS
                Version: 1
                Provider: ECS
              Configuration:
                ClusterName: !Ref Cluster
                ServiceName: !Ref Service
                FileName: images.json
              InputArtifacts:
                - Name: BuildOutput
              RunOrder: 1
Outputs:
  PipelineUrl:
    Value: !Sub https://console.aws.amazon.com/codepipeline/home?region=${AWS::Region}#/view/${Pipeline}
Please upload the above 5 files to S3. Finally, a summary template.
Parameters:
  LaunchType:
    Type: String
    Default: Fargate
    AllowedValues:
      - Fargate
      - EC2
    Description: >
      The launch type for your service. Selecting EC2 will create an Auto
      Scaling group of t2.micro instances for your cluster. See
      https://docs.aws.amazon.com/AmazonECS/latest/developerguide/launch_types.html
      to learn more about launch types.
  TemplateBucket:
    Type: String
    Description: >
      The S3 bucket from which to fetch the templates used by this stack.
    
  CodeCommitRepositoryName:
    Type: String
  ClusterSize:
    Type: Number
    Default: 2
  DesiredCount:
    Type: Number
    Default: 1
  KeyName:
    Description: The EC2 Key Pair to allow SSH access to the instance
    Type: "AWS::EC2::KeyPair::KeyName"
Metadata:
  AWS::CloudFormation::Interface:
    ParameterLabels:
      CodeCommitRepositoryName:
        default: "CodeCommitRepositoryName"
      LaunchType:
        default: "Launch Type"
    ParameterGroups:
      - Label:
          default: Cluster Configuration
        Parameters:
          - LaunchType
      - Label:
          default: CodeCommit Configuration
        Parameters:
          - CodeCommitRepositoryName
      - Label:
          default: Stack Configuration
        Parameters:
          - TemplateBucket
      - Label:
          default: TaskDesiredCount
        Parameters:
          - DesiredCount
Resources:
  Cluster:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Sub "https://s3.amazonaws.com/${TemplateBucket}/ecs-cluster.yaml"
      Parameters:
        LaunchType: !Ref LaunchType
        SecurityGroup: !GetAtt LoadBalancer.Outputs.SecurityGroup
        Subnets: !GetAtt VPC.Outputs.Subnets
        VpcId: !GetAtt VPC.Outputs.VpcId
        ClusterSize: !Ref ClusterSize
        KeyName: !Ref KeyName
  DeploymentPipeline:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Sub "https://s3.amazonaws.com/${TemplateBucket}/deployment-pipeline.yaml"
      Parameters:
        Cluster: !GetAtt Cluster.Outputs.ClusterName
        Service: !GetAtt Service.Outputs.Service
        CodeCommitRepositoryName: !Ref CodeCommitRepositoryName
  LoadBalancer:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Sub "https://s3.amazonaws.com/${TemplateBucket}/load-balancer.yaml"
      Parameters:
        LaunchType: !Ref LaunchType
        Subnets: !GetAtt VPC.Outputs.Subnets
        VpcId: !GetAtt VPC.Outputs.VpcId
  VPC:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Sub "https://s3.amazonaws.com/${TemplateBucket}/vpc.yaml"
      Parameters:
        Name: !Ref AWS::StackName
        VpcCIDR: 10.215.0.0/16
        Subnet1CIDR: 10.215.10.0/24
        Subnet2CIDR: 10.215.20.0/24
  Service:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Sub "https://s3.amazonaws.com/${TemplateBucket}/service.yaml"
      Parameters:
        Cluster: !GetAtt Cluster.Outputs.ClusterName
        LaunchType: !Ref LaunchType
        TargetGroup: !GetAtt LoadBalancer.Outputs.TargetGroup
        SecurityGroup: !GetAtt LoadBalancer.Outputs.SecurityGroup
        Subnets: !GetAtt VPC.Outputs.Subnets
        DesiredCount: !Ref DesiredCount
Outputs:
  ServiceUrl:
    Description: The sample service that is being continuously deployed.
    Value: !GetAtt LoadBalancer.Outputs.ServiceUrl
  PipelineUrl:
    Description: The continuous deployment pipeline in the AWS Management Console.
    Value: !GetAtt DeploymentPipeline.Outputs.PipelineUrl
That is all. Thank you for watching until the end.
Reference article https://qiita.com/chisso/items/3c4dd1af0382d4978288 https://github.com/awslabs/ecs-refarch-continuous-deployment