AWS Amplify CLI / SDK + AWS FargateでNext.jsのSSRに対応する

dev.toのポストで、Amplifyを使ってNext.jsをSSRという記事があがっていました。ざっと読んだところ、「Amplify CLIでAWS Fargateに対してデプロイするようにすればいける」とのことでしたので、試してみました。

プロジェクトのセットアップ

新規にプロジェクトをセットアップします。

Next.js

まずはNext.jsのアプリをセットアップします。--exampleでTypeScript対応版を指定しているのは個人的な好みですので、興味がない方は--example以降を省いてください。

$ npx create-next-app YOUR_APP_NAME --example "https://github.com/wpkyoto/nextjs-starter-typescript/tree/main" 
$ cd YOUR_APP_NAME 

AWS Amplify

まずはAmplify CLIでプロジェクトのセットアップを行います。対話形式で進むので、聞かれた通りに答えればいいのですが、今回の方法でNext.jsを使う場合は2箇所デフォルトから変更する必要があります。

  • Source Directory Path: .
  • Distribution Directory Path(ビルドディレクトリ): .next

% npx @aws-amplify/cli init
 Initializing new Amplify CLI version…
 Done initializing new version.
 Scanning for plugins…
 Plugin scan successful
 Note: It is recommended to run this command from the root of your app directory
 ? Enter a name for the project nextjsamplify
 ? Enter a name for the environment dev
 ? Choose your default editor: Visual Studio Code
 ? Choose the type of app that you're building javascript
 Please tell us about your project
 ? What javascript framework are you using react
 ? Source Directory Path:  .
 ? Distribution Directory Path: .next
 ? Build Command:  npm run-script build
 ? Start Command: npm run-script start
 Using default provider  awscloudformation
 For more information on AWS Profiles, see:
 https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html
 ? Do you want to use an AWS profile? Yes
 ? Please choose the profile you want to use lab

続いてamplify configure projectでコンテナベースのデプロイをオンにします。

% npx @aws-amplify/cli configure project
 Initializing new Amplify CLI version…
 Done initializing new version.
 Scanning for plugins…
 Plugin scan successful
 ? Enter a name for the project nextjsamplify
 ? Choose your default editor: Visual Studio Code
 ? Choose the type of app that you're building javascript
 Please tell us about your project
 ? What javascript framework are you using react
 ? Source Directory Path:  .
 ? Distribution Directory Path: .next
 ? Build Command:  npm run-script build
 ? Start Command: npm run-script start
 Using default provider  awscloudformation

## ここで「Yes」と答える 
 ? Do you want to enable container-based deployments? Yes

 For more information on AWS Profiles, see:
 https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html
 For the awscloudformation provider.
 ? Do you want to update or remove the project level AWS profile? No
 Successfully made configuration changes to your project.

Gitのdiffを見たところ、変更箇所はとても少ないので、手動でも良いかもしれません。

--- a/amplify/.config/project-config.json
 +++ b/amplify/.config/project-config.json
 @@ -8,7 +8,8 @@
        "SourceDir": ".",
        "DistributionDir": ".next",
        "BuildCommand": "npm run-script build",
 "StartCommand": "npm run-script start"
 "StartCommand": "npm run-script start",
 "ServerlessContainers": true
  }
 }, 

Amplify CLIでAWS Fargateを指定してホスティングを追加する

続いてNext.jsを動かすための環境を追加します。amplify add hostingを実行すると、ホスティング環境を3種類から選ぶことができます。

% amplify add hosting
 ? Select the plugin module to execute (Use arrow keys)
 ❯ Hosting with Amplify Console (Managed hosting with custom domains, Continuous deployment) 
   Amazon CloudFront and S3 
   Container-based hosting with AWS Fargate 

ここで「Container-based hosting with AWS Fargate」を指定すると、amplify publishなどでのデプロイがAmplify ConsoleではなくAWS Fargateに対して行われます。

独自ドメインが必須なので要注意

なお、AWS Fargateを指定した場合、ドメインを入力する必要があります。

一度空欄にすればスキップできないかと試したのですが、domain is requiredと言われました。

? Select the plugin module to execute Container-based hosting with AWS Fargate
? Provide your web app endpoint (e.g. app.example.com or www.example.com): 

A registered domain is required. 
You can register a domain using Route 53: aws.amazon.com/route53/ or use an existing domain.

CloudFormationをあとで確認したところ、このドメインは以下の用途で利用されます。

  • CloudFrontのAlias
  • ALBのListener Rule
  • ACMの証明書取得

ACMの証明書取得がメール認証に設定されていますので、whoisなどを確認して確認メールを受けとれるドメインを選びましょう。

? Provide your web app endpoint (e.g. app.example.com or www.example.com): fargate-example.example.com

? Do you want to automatically protect your web app using Amazon Cognito Hosted UI No

あと今回はCognitoのUIもいらないのでNoで省きます。

Dockerfileを編集してNext.jsを実行させる

Dockerfileを以下のように変更して、Next.jsを実行するようにします。

FROM public.ecr.aws/bitnami/node:14.15.1-debian-10-r8
WORKDIR /app

COPY package.json yarn.lock ./
RUN yarn

COPY . .

RUN yarn build
EXPOSE 3000
CMD ["yarn", "start"]

AWSにデプロイ

あとはamplify publishでデプロイするだけです。

CloudFrontのセットアップなどもありますのでかなり時間がかかります。ただし、ACMの証明書チェックがステップに含まれていますので、放置しすぎるとそこで Waitしたままになる罠があるので要注意です。

ACMのドメイン確認メールを確認してApproveすると、後続のデプロイが続行されてデプロイが完了します。

なお、Amplify Console方面にデプロイしていないため、管理画面側ではほぼいない子扱いされています。

「Amplify CLIでの管理」や「Amplify SDKへのインテグレーション」などは可能ですが、「Amplifyコンソールの機能(ドメイン・リダイレクト・ビルド設定など)」は使えないと思った方が良いでしょう。

試してみての感想

主観としては、「Next.jsアプリをAWS Fargateにデプロイして公開するための一式をAmplify CLIで揃えることができる」以上でも以下でもないかなというところです。

デプロイしたアプリに問題が起きた場合のログ確認やデバッグなどはFargateで管理するアプリと同等ですし、細かい設定についてはAmplify CLIが作ったCloudFormationをカスタムすることになります。そうなるとJSONではなくYAML、できればAWS CDKで管理したいという気持ちになるかなぁという印象もうけました。

CodePipelineを使ったビルドパイプラインまで整備してくれるなど、「作られる構成自体」は悪くないなと思います。なので、もしFargateで何かアプリを公開・運用しようと思った場合は、Amplify CLIが作るテンプレートをAWS CDK (TypeScript)でリバースエンジニアリングしてやろいかなと思います。

AWSやコンテナに自信がないという方は、大人しくVercelにデプロイするか、NetlifyやServerless Frameworkのプラグインを使いましょう。

参考

https://dev.to/dabit3/serverless-containers-with-next-js-aws-fargate-and-aws-amplify-17fe

Appendix1: amplify add hostingでAWS Fargateを生成した際に作られたCloudFormationテンプレート

むっちゃ長いです。が、要はAWS Fargate + CloudFront + ACM + Route53 + 関連リソースを立ち上げるテンプレートです。

$ cat amplify/backend/backend-config.json
 {
    "Parameters": {
        "env": {
            "Type": "String"
        },
        "ParamZipPath": {
            "Type": "String",
            "Default": "site.zip"
        },
        "NetworkStackClusterName": {
            "Type": "String"
        },
        "NetworkStackVpcId": {
            "Type": "String"
        },
        "NetworkStackVpcCidrBlock": {
            "Type": "String"
        },
        "NetworkStackSubnetIds": {
            "Type": "CommaDelimitedList"
        },
        "NetworkStackVpcLinkId": {
            "Type": "String"
        },
        "NetworkStackCloudMapNamespaceId": {
            "Type": "String"
        }
    },
    "Conditions": {
        "isAuthCondition": {
            "Fn::And": [
                {
                    "Fn::Equals": [
                        false,
                        true
                    ]
                },
                {
                    "Fn::Not": [
                        {
                            "Fn::Equals": [
                                "",
                                ""
                            ]
                        }
                    ]
                },
                {
                    "Fn::Not": [
                        {
                            "Fn::Equals": [
                                "",
                                ""
                            ]
                        }
                    ]
                }
            ]
        }
    },
    "Resources": {
        "TaskDefinitionTaskRoleFD40A61D": {
            "Type": "AWS::IAM::Role",
            "Properties": {
                "AssumeRolePolicyDocument": {
                    "Statement": [
                        {
                            "Action": "sts:AssumeRole",
                            "Effect": "Allow",
                            "Principal": {
                                "Service": "ecs-tasks.amazonaws.com"
                            }
                        }
                    ],
                    "Version": "2012-10-17"
                }
            }
        },
        "TaskDefinition": {
            "Type": "AWS::ECS::TaskDefinition",
            "Properties": {
                "ContainerDefinitions": [
                    {
                        "Command": [],
                        "EntryPoint": [],
                        "Environment": [],
                        "Essential": true,
                        "Image": {
                            "Fn::Join": [
                                "",
                                [
                                    {
                                        "Fn::Select": [
                                            4,
                                            {
                                                "Fn::Split": [
                                                    ":",
                                                    {
                                                        "Fn::GetAtt": [
                                                            "apiRepository",
                                                            "Arn"
                                                        ]
                                                    }
                                                ]
                                            }
                                        ]
                                    },
                                    ".dkr.ecr.",
                                    {
                                        "Fn::Select": [
                                            3,
                                            {
                                                "Fn::Split": [
                                                    ":",
                                                    {
                                                        "Fn::GetAtt": [
                                                            "apiRepository",
                                                            "Arn"
                                                        ]
                                                    }
                                                ]
                                            }
                                        ]
                                    },
                                    ".",
                                    {
                                        "Ref": "AWS::URLSuffix"
                                    },
                                    "/",
                                    {
                                        "Ref": "apiRepository"
                                    },
                                    ":latest"
                                ]
                            ]
                        },
                        "LogConfiguration": {
                            "LogDriver": "awslogs",
                            "Options": {
                                "awslogs-group": {
                                    "Ref": "apiContainerLogGroupA9A8D168"
                                },
                                "awslogs-stream-prefix": "ecs",
                                "awslogs-region": {
                                    "Ref": "AWS::Region"
                                }
                            }
                        },
                        "Name": "api",
                        "PortMappings": [
                            {
                                "ContainerPort": 80,
                                "Protocol": "tcp"
                            }
                        ],
                        "Secrets": []
                    }
                ],
                "Cpu": "512",
                "ExecutionRoleArn": {
                    "Fn::GetAtt": [
                        "TaskDefinitionExecutionRole8D61C2FB",
                        "Arn"
                    ]
                },
                "Family": "amplify-nextjsamplify-dev-190351-site",
                "Memory": "1024",
                "NetworkMode": "awsvpc",
                "RequiresCompatibilities": [
                    "FARGATE"
                ],
                "TaskRoleArn": {
                    "Fn::GetAtt": [
                        "TaskDefinitionTaskRoleFD40A61D",
                        "Arn"
                    ]
                }
            }
        },
        "TaskDefinitionExecutionRole8D61C2FB": {
            "Type": "AWS::IAM::Role",
            "Properties": {
                "AssumeRolePolicyDocument": {
                    "Statement": [
                        {
                            "Action": "sts:AssumeRole",
                            "Effect": "Allow",
                            "Principal": {
                                "Service": "ecs-tasks.amazonaws.com"
                            }
                        }
                    ],
                    "Version": "2012-10-17"
                }
            }
        },
        "TaskDefinitionExecutionRoleDefaultPolicy1F3406F5": {
            "Type": "AWS::IAM::Policy",
            "Properties": {
                "PolicyDocument": {
                    "Statement": [
                        {
                            "Action": [
                                "ecr:BatchCheckLayerAvailability",
                                "ecr:GetDownloadUrlForLayer",
                                "ecr:BatchGetImage"
                            ],
                            "Effect": "Allow",
                            "Resource": {
                                "Fn::GetAtt": [
                                    "apiRepository",
                                    "Arn"
                                ]
                            }
                        },
                        {
                            "Action": "ecr:GetAuthorizationToken",
                            "Effect": "Allow",
                            "Resource": "*"
                        },
                        {
                            "Action": [
                                "logs:CreateLogStream",
                                "logs:PutLogEvents"
                            ],
                            "Effect": "Allow",
                            "Resource": {
                                "Fn::Join": [
                                    "",
                                    [
                                        "arn:",
                                        {
                                            "Ref": "AWS::Partition"
                                        },
                                        ":logs:",
                                        {
                                            "Ref": "AWS::Region"
                                        },
                                        ":",
                                        {
                                            "Ref": "AWS::AccountId"
                                        },
                                        ":log-group:",
                                        {
                                            "Ref": "apiContainerLogGroupA9A8D168"
                                        },
                                        ":*"
                                    ]
                                ]
                            }
                        }
                    ],
                    "Version": "2012-10-17"
                },
                "PolicyName": "TaskDefinitionExecutionRoleDefaultPolicy1F3406F5",
                "Roles": [
                    {
                        "Ref": "TaskDefinitionExecutionRole8D61C2FB"
                    }
                ]
            }
        },
        "apiContainerLogGroupA9A8D168": {
            "Type": "AWS::Logs::LogGroup",
            "Properties": {
                "LogGroupName": "/ecs/amplify-nextjsamplify-dev-190351-site-api",
                "RetentionInDays": 30
            },
            "UpdateReplacePolicy": "Delete",
            "DeletionPolicy": "Delete"
        },
        "apiRepository": {
            "Type": "AWS::ECR::Repository",
            "Properties": {
                "LifecyclePolicy": {
                    "LifecyclePolicyText": "{\"rules\":[{\"rulePriority\":10,\"selection\":{\"tagStatus\":\"tagged\",\"tagPrefixList\":[\"latest\"],\"countType\":\"imageCountMoreThan\",\"countNumber\":1},\"action\":{\"type\":\"expire\"}},{\"rulePriority\":100,\"selection\":{\"tagStatus\":\"any\",\"countType\":\"sinceImagePushed\",\"countNumber\":7,\"countUnit\":\"days\"},\"action\":{\"type\":\"expire\"}}]}"
                },
                "RepositoryName": "amplify-nextjsamplify-dev-190351-hosting-site-api"
            },
            "UpdateReplacePolicy": "Retain",
            "DeletionPolicy": "Retain"
        },
        "ServiceSG": {
            "Type": "AWS::EC2::SecurityGroup",
            "Properties": {
                "GroupDescription": "Service SecurityGroup",
                "SecurityGroupEgress": [
                    {
                        "CidrIp": "0.0.0.0/0",
                        "Description": "Allow all outbound traffic by default",
                        "IpProtocol": "-1"
                    }
                ],
                "SecurityGroupIngress": [
                    {
                        "CidrIp": {
                            "Ref": "NetworkStackVpcCidrBlock"
                        },
                        "FromPort": 80,
                        "IpProtocol": "tcp",
                        "ToPort": 80
                    },
                    {
                        "FromPort": 80,
                        "IpProtocol": "tcp",
                        "SourceSecurityGroupId": {
                            "Fn::GetAtt": [
                                "AlbSecurityGroup",
                                "GroupId"
                            ]
                        },
                        "ToPort": 80
                    }
                ],
                "VpcId": {
                    "Ref": "NetworkStackVpcId"
                }
            }
        },
        "Service": {
            "Type": "AWS::ECS::Service",
            "Properties": {
                "Cluster": {
                    "Ref": "NetworkStackClusterName"
                },
                "DesiredCount": 0,
                "LaunchType": "FARGATE",
                "LoadBalancers": [
                    {
                        "ContainerName": "api",
                        "ContainerPort": 80,
                        "TargetGroupArn": {
                            "Ref": "TargetGroup"
                        }
                    }
                ],
                "NetworkConfiguration": {
                    "AwsvpcConfiguration": {
                        "AssignPublicIp": "ENABLED",
                        "SecurityGroups": [
                            {
                                "Fn::GetAtt": [
                                    "ServiceSG",
                                    "GroupId"
                                ]
                            }
                        ],
                        "Subnets": {
                            "Ref": "NetworkStackSubnetIds"
                        }
                    }
                },
                "ServiceName": "site-service-api-80",
                "TaskDefinition": {
                    "Ref": "TaskDefinition"
                }
            },
            "DependsOn": [
                "AlbListener",
                "AlbListenerRule"
            ]
        },
        "ApiPipelineCodeBuildProjectRoleCFB98631": {
            "Type": "AWS::IAM::Role",
            "Properties": {
                "AssumeRolePolicyDocument": {
                    "Statement": [
                        {
                            "Action": "sts:AssumeRole",
                            "Effect": "Allow",
                            "Principal": {
                                "Service": "codebuild.amazonaws.com"
                            }
                        }
                    ],
                    "Version": "2012-10-17"
                }
            }
        },
        "ApiPipelineCodeBuildProjectRoleDefaultPolicyAF1730E7": {
            "Type": "AWS::IAM::Policy",
            "Properties": {
                "PolicyDocument": {
                    "Statement": [
                        {
                            "Action": [
                                "logs:CreateLogGroup",
                                "logs:CreateLogStream",
                                "logs:PutLogEvents"
                            ],
                            "Effect": "Allow",
                            "Resource": [
                                {
                                    "Fn::Join": [
                                        "",
                                        [
                                            "arn:",
                                            {
                                                "Ref": "AWS::Partition"
                                            },
                                            ":logs:",
                                            {
                                                "Ref": "AWS::Region"
                                            },
                                            ":",
                                            {
                                                "Ref": "AWS::AccountId"
                                            },
                                            ":log-group:/aws/codebuild/",
                                            {
                                                "Ref": "ApiPipelineCodeBuildProject117EE1BE"
                                            }
                                        ]
                                    ]
                                },
                                {
                                    "Fn::Join": [
                                        "",
                                        [
                                            "arn:",
                                            {
                                                "Ref": "AWS::Partition"
                                            },
                                            ":logs:",
                                            {
                                                "Ref": "AWS::Region"
                                            },
                                            ":",
                                            {
                                                "Ref": "AWS::AccountId"
                                            },
                                            ":log-group:/aws/codebuild/",
                                            {
                                                "Ref": "ApiPipelineCodeBuildProject117EE1BE"
                                            },
                                            ":*"
                                        ]
                                    ]
                                }
                            ]
                        },
                        {
                            "Action": [
                                "codebuild:CreateReportGroup",
                                "codebuild:CreateReport",
                                "codebuild:UpdateReport",
                                "codebuild:BatchPutTestCases",
                                "codebuild:BatchPutCodeCoverages"
                            ],
                            "Effect": "Allow",
                            "Resource": {
                                "Fn::Join": [
                                    "",
                                    [
                                        "arn:",
                                        {
                                            "Ref": "AWS::Partition"
                                        },
                                        ":codebuild:",
                                        {
                                            "Ref": "AWS::Region"
                                        },
                                        ":",
                                        {
                                            "Ref": "AWS::AccountId"
                                        },
                                        ":report-group/",
                                        {
                                            "Ref": "ApiPipelineCodeBuildProject117EE1BE"
                                        },
                                        "-*"
                                    ]
                                ]
                            }
                        },
                        {
                            "Action": [
                                "ecr:GetAuthorizationToken",
                                "ecr:BatchGetImage",
                                "ecr:BatchGetDownloadUrlForLayer",
                                "ecr:InitiateLayerUpload",
                                "ecr:BatchCheckLayerAvailability",
                                "ecr:UploadLayerPart",
                                "ecr:CompleteLayerUpload",
                                "ecr:PutImage"
                            ],
                            "Effect": "Allow",
                            "Resource": "*"
                        },
                        {
                            "Action": [
                                "s3:GetObject*",
                                "s3:GetBucket*",
                                "s3:List*",
                                "s3:DeleteObject*",
                                "s3:PutObject*",
                                "s3:Abort*"
                            ],
                            "Effect": "Allow",
                            "Resource": [
                                {
                                    "Fn::Join": [
                                        "",
                                        [
                                            "arn:",
                                            {
                                                "Ref": "AWS::Partition"
                                            },
                                            ":s3:::amplify-nextjsamplify-dev-190351-deployment"
                                        ]
                                    ]
                                },
                                {
                                    "Fn::Join": [
                                        "",
                                        [
                                            "arn:",
                                            {
                                                "Ref": "AWS::Partition"
                                            },
                                            ":s3:::amplify-nextjsamplify-dev-190351-deployment/*"
                                        ]
                                    ]
                                }
                            ]
                        }
                    ],
                    "Version": "2012-10-17"
                },
                "PolicyName": "ApiPipelineCodeBuildProjectRoleDefaultPolicyAF1730E7",
                "Roles": [
                    {
                        "Ref": "ApiPipelineCodeBuildProjectRoleCFB98631"
                    }
                ]
            }
        },
        "ApiPipelineCodeBuildProject117EE1BE": {
            "Type": "AWS::CodeBuild::Project",
            "Properties": {
                "Artifacts": {
                    "Type": "CODEPIPELINE"
                },
                "Environment": {
                    "ComputeType": "BUILD_GENERAL1_SMALL",
                    "Image": "aws/codebuild/standard:4.0",
                    "ImagePullCredentialsType": "CODEBUILD",
                    "PrivilegedMode": true,
                    "Type": "LINUX_CONTAINER"
                },
                "ServiceRole": {
                    "Fn::GetAtt": [
                        "ApiPipelineCodeBuildProjectRoleCFB98631",
                        "Arn"
                    ]
                },
                "Source": {
                    "Type": "CODEPIPELINE"
                },
                "EncryptionKey": "alias/aws/s3"
            }
        },
        "PreDeployLambdaServiceRole87EE44C9": {
            "Type": "AWS::IAM::Role",
            "Properties": {
                "AssumeRolePolicyDocument": {
                    "Statement": [
                        {
                            "Action": "sts:AssumeRole",
                            "Effect": "Allow",
                            "Principal": {
                                "Service": "lambda.amazonaws.com"
                            }
                        }
                    ],
                    "Version": "2012-10-17"
                },
                "ManagedPolicyArns": [
                    {
                        "Fn::Join": [
                            "",
                            [
                                "arn:",
                                {
                                    "Ref": "AWS::Partition"
                                },
                                ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
                            ]
                        ]
                    }
                ]
            }
        },
        "PreDeployLambdaServiceRoleDefaultPolicyD9F42A87": {
            "Type": "AWS::IAM::Policy",
            "Properties": {
                "PolicyDocument": {
                    "Statement": [
                        {
                            "Action": "ecs:UpdateService",
                            "Effect": "Allow",
                            "Resource": {
                                "Ref": "Service"
                            }
                        },
                        {
                            "Action": [
                                "codepipeline:PutJobSuccessResult",
                                "codepipeline:PutJobFailureResult"
                            ],
                            "Effect": "Allow",
                            "Resource": "*"
                        }
                    ],
                    "Version": "2012-10-17"
                },
                "PolicyName": "PreDeployLambdaServiceRoleDefaultPolicyD9F42A87",
                "Roles": [
                    {
                        "Ref": "PreDeployLambdaServiceRole87EE44C9"
                    }
                ]
            }
        },
        "PreDeployLambdaF7CAA99F": {
            "Type": "AWS::Lambda::Function",
            "Properties": {
                "Code": {
                    "ZipFile": "const AWS = require('aws-sdk');\n\nconst codepipeline = new AWS.CodePipeline();\nconst ecs = new AWS.ECS();\n\nconst { DESIRED_COUNT: desiredCountStr, CLUSTER_NAME: cluster, SERVICE_NAME: service } = process.env;\n\nconst desiredCount = parseInt(desiredCountStr, 10);\n\nexports.handler = async function({ 'CodePipeline.job': { id: jobId } }) {\n  await ecs\n    .updateService({\n      service,\n      cluster,\n      desiredCount,\n    })\n    .promise();\n\n  return await codepipeline.putJobSuccessResult({ jobId }).promise();\n};\n"
                },
                "Handler": "index.handler",
                "Role": {
                    "Fn::GetAtt": [
                        "PreDeployLambdaServiceRole87EE44C9",
                        "Arn"
                    ]
                },
                "Runtime": "nodejs12.x",
                "Environment": {
                    "Variables": {
                        "DESIRED_COUNT": "1",
                        "CLUSTER_NAME": {
                            "Ref": "NetworkStackClusterName"
                        },
                        "SERVICE_NAME": "site-service-api-80"
                    }
                },
                "Timeout": 15
            },
            "DependsOn": [
                "PreDeployLambdaServiceRoleDefaultPolicyD9F42A87",
                "PreDeployLambdaServiceRole87EE44C9"
            ]
        },
        "ApiPipelinePipelineRole8C805448": {
            "Type": "AWS::IAM::Role",
            "Properties": {
                "AssumeRolePolicyDocument": {
                    "Statement": [
                        {
                            "Action": "sts:AssumeRole",
                            "Effect": "Allow",
                            "Principal": {
                                "Service": "codepipeline.amazonaws.com"
                            }
                        }
                    ],
                    "Version": "2012-10-17"
                }
            },
            "DependsOn": [
                "Service"
            ]
        },
        "ApiPipelinePipelineRoleDefaultPolicyB3AD67CF": {
            "Type": "AWS::IAM::Policy",
            "Properties": {
                "PolicyDocument": {
                    "Statement": [
                        {
                            "Action": [
                                "s3:GetObject*",
                                "s3:GetBucket*",
                                "s3:List*",
                                "s3:DeleteObject*",
                                "s3:PutObject*",
                                "s3:Abort*"
                            ],
                            "Effect": "Allow",
                            "Resource": [
                                {
                                    "Fn::Join": [
                                        "",
                                        [
                                            "arn:",
                                            {
                                                "Ref": "AWS::Partition"
                                            },
                                            ":s3:::amplify-nextjsamplify-dev-190351-deployment"
                                        ]
                                    ]
                                },
                                {
                                    "Fn::Join": [
                                        "",
                                        [
                                            "arn:",
                                            {
                                                "Ref": "AWS::Partition"
                                            },
                                            ":s3:::amplify-nextjsamplify-dev-190351-deployment/*"
                                        ]
                                    ]
                                }
                            ]
                        },
                        {
                            "Action": "sts:AssumeRole",
                            "Effect": "Allow",
                            "Resource": {
                                "Fn::GetAtt": [
                                    "ApiPipelinePipelineSourceCodePipelineActionRole59037A7A",
                                    "Arn"
                                ]
                            }
                        },
                        {
                            "Action": "sts:AssumeRole",
                            "Effect": "Allow",
                            "Resource": {
                                "Fn::GetAtt": [
                                    "ApiPipelinePipelineBuildCodePipelineActionRole04E6F13B",
                                    "Arn"
                                ]
                            }
                        },
                        {
                            "Action": "sts:AssumeRole",
                            "Effect": "Allow",
                            "Resource": {
                                "Fn::GetAtt": [
                                    "ApiPipelinePipelinePredeployCodePipelineActionRole387C06F4",
                                    "Arn"
                                ]
                            }
                        },
                        {
                            "Action": "sts:AssumeRole",
                            "Effect": "Allow",
                            "Resource": {
                                "Fn::GetAtt": [
                                    "ApiPipelinePipelineDeployCodePipelineActionRoleEA4B81BD",
                                    "Arn"
                                ]
                            }
                        }
                    ],
                    "Version": "2012-10-17"
                },
                "PolicyName": "ApiPipelinePipelineRoleDefaultPolicyB3AD67CF",
                "Roles": [
                    {
                        "Ref": "ApiPipelinePipelineRole8C805448"
                    }
                ]
            },
            "DependsOn": [
                "Service"
            ]
        },
        "ApiPipelinePipeline68596884": {
            "Type": "AWS::CodePipeline::Pipeline",
            "Properties": {
                "RoleArn": {
                    "Fn::GetAtt": [
                        "ApiPipelinePipelineRole8C805448",
                        "Arn"
                    ]
                },
                "Stages": [
                    {
                        "Actions": [
                            {
                                "ActionTypeId": {
                                    "Category": "Source",
                                    "Owner": "AWS",
                                    "Provider": "S3",
                                    "Version": "1"
                                },
                                "Configuration": {
                                    "S3Bucket": "amplify-nextjsamplify-dev-190351-deployment",
                                    "S3ObjectKey": {
                                        "Ref": "ParamZipPath"
                                    }
                                },
                                "Name": "Source",
                                "OutputArtifacts": [
                                    {
                                        "Name": "SourceArtifact"
                                    }
                                ],
                                "RoleArn": {
                                    "Fn::GetAtt": [
                                        "ApiPipelinePipelineSourceCodePipelineActionRole59037A7A",
                                        "Arn"
                                    ]
                                },
                                "RunOrder": 1
                            }
                        ],
                        "Name": "Source"
                    },
                    {
                        "Actions": [
                            {
                                "ActionTypeId": {
                                    "Category": "Build",
                                    "Owner": "AWS",
                                    "Provider": "CodeBuild",
                                    "Version": "1"
                                },
                                "Configuration": {
                                    "ProjectName": {
                                        "Ref": "ApiPipelineCodeBuildProject117EE1BE"
                                    },
                                    "EnvironmentVariables": {
                                        "Fn::Join": [
                                            "",
                                            [
                                                "[{\"name\":\"AWS_ACCOUNT_ID\",\"type\":\"PLAINTEXT\",\"value\":\"",
                                                {
                                                    "Ref": "AWS::AccountId"
                                                },
                                                "\"},{\"name\":\"api_REPOSITORY_URI\",\"type\":\"PLAINTEXT\",\"value\":\"",
                                                {
                                                    "Fn::Select": [
                                                        4,
                                                        {
                                                            "Fn::Split": [
                                                                ":",
                                                                {
                                                                    "Fn::GetAtt": [
                                                                        "apiRepository",
                                                                        "Arn"
                                                                    ]
                                                                }
                                                            ]
                                                        }
                                                    ]
                                                },
                                                ".dkr.ecr.",
                                                {
                                                    "Fn::Select": [
                                                        3,
                                                        {
                                                            "Fn::Split": [
                                                                ":",
                                                                {
                                                                    "Fn::GetAtt": [
                                                                        "apiRepository",
                                                                        "Arn"
                                                                    ]
                                                                }
                                                            ]
                                                        }
                                                    ]
                                                },
                                                ".",
                                                {
                                                    "Ref": "AWS::URLSuffix"
                                                },
                                                "/",
                                                {
                                                    "Ref": "apiRepository"
                                                },
                                                "\"}]"
                                            ]
                                        ]
                                    }
                                },
                                "InputArtifacts": [
                                    {
                                        "Name": "SourceArtifact"
                                    }
                                ],
                                "Name": "Build",
                                "OutputArtifacts": [
                                    {
                                        "Name": "BuildArtifact"
                                    }
                                ],
                                "RoleArn": {
                                    "Fn::GetAtt": [
                                        "ApiPipelinePipelineBuildCodePipelineActionRole04E6F13B",
                                        "Arn"
                                    ]
                                },
                                "RunOrder": 1
                            }
                        ],
                        "Name": "Build"
                    },
                    {
                        "Actions": [
                            {
                                "ActionTypeId": {
                                    "Category": "Invoke",
                                    "Owner": "AWS",
                                    "Provider": "Lambda",
                                    "Version": "1"
                                },
                                "Configuration": {
                                    "FunctionName": {
                                        "Ref": "PreDeployLambdaF7CAA99F"
                                    }
                                },
                                "Name": "Predeploy",
                                "RoleArn": {
                                    "Fn::GetAtt": [
                                        "ApiPipelinePipelinePredeployCodePipelineActionRole387C06F4",
                                        "Arn"
                                    ]
                                },
                                "RunOrder": 1
                            }
                        ],
                        "Name": "Predeploy"
                    },
                    {
                        "Actions": [
                            {
                                "ActionTypeId": {
                                    "Category": "Deploy",
                                    "Owner": "AWS",
                                    "Provider": "ECS",
                                    "Version": "1"
                                },
                                "Configuration": {
                                    "ClusterName": {
                                        "Ref": "NetworkStackClusterName"
                                    },
                                    "ServiceName": "site-service-api-80"
                                },
                                "InputArtifacts": [
                                    {
                                        "Name": "BuildArtifact"
                                    }
                                ],
                                "Name": "Deploy",
                                "RoleArn": {
                                    "Fn::GetAtt": [
                                        "ApiPipelinePipelineDeployCodePipelineActionRoleEA4B81BD",
                                        "Arn"
                                    ]
                                },
                                "RunOrder": 1
                            }
                        ],
                        "Name": "Deploy"
                    }
                ],
                "ArtifactStore": {
                    "Location": "amplify-nextjsamplify-dev-190351-deployment",
                    "Type": "S3"
                },
                "Name": "amplify-nextjsamplify-dev-190351-site-service-api-80"
            },
            "DependsOn": [
                "ApiPipelinePipelineRoleDefaultPolicyB3AD67CF",
                "ApiPipelinePipelineRole8C805448",
                "Service"
            ]
        },
        "ApiPipelinePipelineSourceCodePipelineActionRole59037A7A": {
            "Type": "AWS::IAM::Role",
            "Properties": {
                "AssumeRolePolicyDocument": {
                    "Statement": [
                        {
                            "Action": "sts:AssumeRole",
                            "Effect": "Allow",
                            "Principal": {
                                "AWS": {
                                    "Fn::Join": [
                                        "",
                                        [
                                            "arn:",
                                            {
                                                "Ref": "AWS::Partition"
                                            },
                                            ":iam::",
                                            {
                                                "Ref": "AWS::AccountId"
                                            },
                                            ":root"
                                        ]
                                    ]
                                }
                            }
                        }
                    ],
                    "Version": "2012-10-17"
                }
            },
            "DependsOn": [
                "Service"
            ]
        },
        "ApiPipelinePipelineSourceCodePipelineActionRoleDefaultPolicyE5B48B86": {
            "Type": "AWS::IAM::Policy",
            "Properties": {
                "PolicyDocument": {
                    "Statement": [
                        {
                            "Action": [
                                "s3:GetObject*",
                                "s3:GetBucket*",
                                "s3:List*"
                            ],
                            "Effect": "Allow",
                            "Resource": [
                                {
                                    "Fn::Join": [
                                        "",
                                        [
                                            "arn:",
                                            {
                                                "Ref": "AWS::Partition"
                                            },
                                            ":s3:::amplify-nextjsamplify-dev-190351-deployment"
                                        ]
                                    ]
                                },
                                {
                                    "Fn::Join": [
                                        "",
                                        [
                                            "arn:",
                                            {
                                                "Ref": "AWS::Partition"
                                            },
                                            ":s3:::amplify-nextjsamplify-dev-190351-deployment/*"
                                        ]
                                    ]
                                }
                            ]
                        },
                        {
                            "Action": [
                                "s3:DeleteObject*",
                                "s3:PutObject*",
                                "s3:Abort*"
                            ],
                            "Effect": "Allow",
                            "Resource": [
                                {
                                    "Fn::Join": [
                                        "",
                                        [
                                            "arn:",
                                            {
                                                "Ref": "AWS::Partition"
                                            },
                                            ":s3:::amplify-nextjsamplify-dev-190351-deployment"
                                        ]
                                    ]
                                },
                                {
                                    "Fn::Join": [
                                        "",
                                        [
                                            "arn:",
                                            {
                                                "Ref": "AWS::Partition"
                                            },
                                            ":s3:::amplify-nextjsamplify-dev-190351-deployment/*"
                                        ]
                                    ]
                                }
                            ]
                        }
                    ],
                    "Version": "2012-10-17"
                },
                "PolicyName": "ApiPipelinePipelineSourceCodePipelineActionRoleDefaultPolicyE5B48B86",
                "Roles": [
                    {
                        "Ref": "ApiPipelinePipelineSourceCodePipelineActionRole59037A7A"
                    }
                ]
            },
            "DependsOn": [
                "Service"
            ]
        },
        "ApiPipelinePipelineBuildCodePipelineActionRole04E6F13B": {
            "Type": "AWS::IAM::Role",
            "Properties": {
                "AssumeRolePolicyDocument": {
                    "Statement": [
                        {
                            "Action": "sts:AssumeRole",
                            "Effect": "Allow",
                            "Principal": {
                                "AWS": {
                                    "Fn::Join": [
                                        "",
                                        [
                                            "arn:",
                                            {
                                                "Ref": "AWS::Partition"
                                            },
                                            ":iam::",
                                            {
                                                "Ref": "AWS::AccountId"
                                            },
                                            ":root"
                                        ]
                                    ]
                                }
                            }
                        }
                    ],
                    "Version": "2012-10-17"
                }
            },
            "DependsOn": [
                "Service"
            ]
        },
        "ApiPipelinePipelineBuildCodePipelineActionRoleDefaultPolicy821C7A3A": {
            "Type": "AWS::IAM::Policy",
            "Properties": {
                "PolicyDocument": {
                    "Statement": [
                        {
                            "Action": [
                                "codebuild:BatchGetBuilds",
                                "codebuild:StartBuild",
                                "codebuild:StopBuild"
                            ],
                            "Effect": "Allow",
                            "Resource": {
                                "Fn::GetAtt": [
                                    "ApiPipelineCodeBuildProject117EE1BE",
                                    "Arn"
                                ]
                            }
                        }
                    ],
                    "Version": "2012-10-17"
                },
                "PolicyName": "ApiPipelinePipelineBuildCodePipelineActionRoleDefaultPolicy821C7A3A",
                "Roles": [
                    {
                        "Ref": "ApiPipelinePipelineBuildCodePipelineActionRole04E6F13B"
                    }
                ]
            },
            "DependsOn": [
                "Service"
            ]
        },
        "ApiPipelinePipelinePredeployCodePipelineActionRole387C06F4": {
            "Type": "AWS::IAM::Role",
            "Properties": {
                "AssumeRolePolicyDocument": {
                    "Statement": [
                        {
                            "Action": "sts:AssumeRole",
                            "Effect": "Allow",
                            "Principal": {
                                "AWS": {
                                    "Fn::Join": [
                                        "",
                                        [
                                            "arn:",
                                            {
                                                "Ref": "AWS::Partition"
                                            },
                                            ":iam::",
                                            {
                                                "Ref": "AWS::AccountId"
                                            },
                                            ":root"
                                        ]
                                    ]
                                }
                            }
                        }
                    ],
                    "Version": "2012-10-17"
                }
            },
            "DependsOn": [
                "Service"
            ]
        },
        "ApiPipelinePipelinePredeployCodePipelineActionRoleDefaultPolicy715F96BC": {
            "Type": "AWS::IAM::Policy",
            "Properties": {
                "PolicyDocument": {
                    "Statement": [
                        {
                            "Action": "lambda:ListFunctions",
                            "Effect": "Allow",
                            "Resource": "*"
                        },
                        {
                            "Action": "lambda:InvokeFunction",
                            "Effect": "Allow",
                            "Resource": {
                                "Fn::GetAtt": [
                                    "PreDeployLambdaF7CAA99F",
                                    "Arn"
                                ]
                            }
                        }
                    ],
                    "Version": "2012-10-17"
                },
                "PolicyName": "ApiPipelinePipelinePredeployCodePipelineActionRoleDefaultPolicy715F96BC",
                "Roles": [
                    {
                        "Ref": "ApiPipelinePipelinePredeployCodePipelineActionRole387C06F4"
                    }
                ]
            },
            "DependsOn": [
                "Service"
            ]
        },
        "ApiPipelinePipelineDeployCodePipelineActionRoleEA4B81BD": {
            "Type": "AWS::IAM::Role",
            "Properties": {
                "AssumeRolePolicyDocument": {
                    "Statement": [
                        {
                            "Action": "sts:AssumeRole",
                            "Effect": "Allow",
                            "Principal": {
                                "AWS": {
                                    "Fn::Join": [
                                        "",
                                        [
                                            "arn:",
                                            {
                                                "Ref": "AWS::Partition"
                                            },
                                            ":iam::",
                                            {
                                                "Ref": "AWS::AccountId"
                                            },
                                            ":root"
                                        ]
                                    ]
                                }
                            }
                        }
                    ],
                    "Version": "2012-10-17"
                }
            },
            "DependsOn": [
                "Service"
            ]
        },
        "ApiPipelinePipelineDeployCodePipelineActionRoleDefaultPolicyB03FE2A4": {
            "Type": "AWS::IAM::Policy",
            "Properties": {
                "PolicyDocument": {
                    "Statement": [
                        {
                            "Action": [
                                "ecs:DescribeServices",
                                "ecs:DescribeTaskDefinition",
                                "ecs:DescribeTasks",
                                "ecs:ListTasks",
                                "ecs:RegisterTaskDefinition",
                                "ecs:UpdateService"
                            ],
                            "Effect": "Allow",
                            "Resource": "*"
                        },
                        {
                            "Action": "iam:PassRole",
                            "Condition": {
                                "StringEqualsIfExists": {
                                    "iam:PassedToService": [
                                        "ec2.amazonaws.com",
                                        "ecs-tasks.amazonaws.com"
                                    ]
                                }
                            },
                            "Effect": "Allow",
                            "Resource": "*"
                        },
                        {
                            "Action": [
                                "s3:GetObject*",
                                "s3:GetBucket*",
                                "s3:List*"
                            ],
                            "Effect": "Allow",
                            "Resource": [
                                {
                                    "Fn::Join": [
                                        "",
                                        [
                                            "arn:",
                                            {
                                                "Ref": "AWS::Partition"
                                            },
                                            ":s3:::amplify-nextjsamplify-dev-190351-deployment"
                                        ]
                                    ]
                                },
                                {
                                    "Fn::Join": [
                                        "",
                                        [
                                            "arn:",
                                            {
                                                "Ref": "AWS::Partition"
                                            },
                                            ":s3:::amplify-nextjsamplify-dev-190351-deployment/*"
                                        ]
                                    ]
                                }
                            ]
                        }
                    ],
                    "Version": "2012-10-17"
                },
                "PolicyName": "ApiPipelinePipelineDeployCodePipelineActionRoleDefaultPolicyB03FE2A4",
                "Roles": [
                    {
                        "Ref": "ApiPipelinePipelineDeployCodePipelineActionRoleEA4B81BD"
                    }
                ]
            },
            "DependsOn": [
                "Service"
            ]
        },
        "Certificate": {
            "Type": "AWS::CertificateManager::Certificate",
            "Properties": {
                "DomainName": "*.wp-kyoto.net",
                "DomainValidationOptions": [
                    {
                        "DomainName": "*.wp-kyoto.net",
                        "ValidationDomain": "wp-kyoto.net"
                    }
                ],
                "ValidationMethod": "EMAIL"
            }
        },
        "TargetGroup": {
            "Type": "AWS::ElasticLoadBalancingV2::TargetGroup",
            "Properties": {
                "HealthCheckIntervalSeconds": 90,
                "HealthCheckPath": "/",
                "HealthCheckTimeoutSeconds": 60,
                "HealthyThresholdCount": 2,
                "Port": 80,
                "Protocol": "HTTP",
                "TargetType": "ip",
                "UnhealthyThresholdCount": 2,
                "VpcId": {
                    "Ref": "NetworkStackVpcId"
                }
            }
        },
        "AlbSecurityGroup": {
            "Type": "AWS::EC2::SecurityGroup",
            "Properties": {
                "GroupDescription": "ALB Security Group",
                "SecurityGroupEgress": [
                    {
                        "CidrIp": "0.0.0.0/0",
                        "Description": "Allow all outbound traffic by default",
                        "IpProtocol": "-1"
                    }
                ],
                "SecurityGroupIngress": [
                    {
                        "CidrIp": "0.0.0.0/0",
                        "Description": "Allow from anyone on port 443",
                        "FromPort": 443,
                        "IpProtocol": "tcp",
                        "ToPort": 443
                    }
                ],
                "VpcId": {
                    "Ref": "NetworkStackVpcId"
                }
            }
        },
        "LoadBalancer": {
            "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer",
            "Properties": {
                "LoadBalancerAttributes": [
                    {
                        "Key": "deletion_protection.enabled",
                        "Value": "false"
                    }
                ],
                "Scheme": "internet-facing",
                "SecurityGroups": [
                    {
                        "Fn::GetAtt": [
                            "AlbSecurityGroup",
                            "GroupId"
                        ]
                    }
                ],
                "Subnets": {
                    "Ref": "NetworkStackSubnetIds"
                },
                "Type": "application"
            }
        },
        "AlbListener": {
            "Type": "AWS::ElasticLoadBalancingV2::Listener",
            "Properties": {
                "DefaultActions": [
                    {
                        "FixedResponseConfig": {
                            "StatusCode": "403"
                        },
                        "Type": "fixed-response"
                    }
                ],
                "LoadBalancerArn": {
                    "Ref": "LoadBalancer"
                },
                "Port": 443,
                "Protocol": "HTTPS",
                "Certificates": [
                    {
                        "CertificateArn": {
                            "Ref": "Certificate"
                        }
                    }
                ]
            }
        },
        "AlbListenerRule": {
            "Type": "AWS::ElasticLoadBalancingV2::ListenerRule",
            "Properties": {
                "Actions": [
                    {
                        "Order": 1,
                        "TargetGroupArn": {
                            "Ref": "TargetGroup"
                        },
                        "Type": "forward"
                    }
                ],
                "Conditions": [
                    {
                        "Field": "host-header",
                        "HostHeaderConfig": {
                            "Values": [
                                "fargate-example.wp-kyoto.net"
                            ]
                        }
                    },
                    {
                        "Field": "http-header",
                        "HttpHeaderConfig": {
                            "HttpHeaderName": "x-cf-token",
                            "Values": [
                                "b3c5fae4-3c01-445d-a85f-4c03b3946a0d"
                            ]
                        }
                    }
                ],
                "ListenerArn": {
                    "Ref": "AlbListener"
                },
                "Priority": 1
            }
        },
        "Distribution": {
            "Type": "AWS::CloudFront::Distribution",
            "Properties": {
                "DistributionConfig": {
                    "Aliases": [
                        "fargate-example.wp-kyoto.net"
                    ],
                    "DefaultCacheBehavior": {
                        "ForwardedValues": {
                            "Cookies": {
                                "Forward": "all"
                            },
                            "Headers": [
                                "*"
                            ],
                            "QueryString": true
                        },
                        "TargetOriginId": "LoadBalancer-origin",
                        "ViewerProtocolPolicy": "redirect-to-https"
                    },
                    "Enabled": true,
                    "HttpVersion": "http2",
                    "IPV6Enabled": true,
                    "Origins": [
                        {
                            "CustomOriginConfig": {
                                "OriginProtocolPolicy": "https-only"
                            },
                            "DomainName": "lb-amplify-nextjsamplify-dev-190351.wp-kyoto.net",
                            "Id": "LoadBalancer-origin",
                            "OriginCustomHeaders": [
                                {
                                    "HeaderName": "x-cf-token",
                                    "HeaderValue": "b3c5fae4-3c01-445d-a85f-4c03b3946a0d"
                                }
                            ]
                        }
                    ],
                    "ViewerCertificate": {
                        "AcmCertificateArn": {
                            "Ref": "Certificate"
                        },
                        "MinimumProtocolVersion": "TLSv1.2_2019",
                        "SslSupportMethod": "sni-only"
                    }
                }
            }
        }
    },
    "Outputs": {
        "ServiceName": {
            "Value": "site-service-api-80"
        },
        "ClusterName": {
            "Value": {
                "Ref": "NetworkStackClusterName"
            }
        },
        "PipelineName": {
            "Value": "amplify-nextjsamplify-dev-190351-site-service-api-80"
        },
        "ContainerNames": {
            "Value": "api"
        },
        "PipelineUrl": {
            "Value": {
                "Fn::Join": [
                    "",
                    [
                        "https://",
                        {
                            "Ref": "AWS::Region"
                        },
                        ".console.aws.amazon.com/codesuite/codepipeline/pipelines/amplify-nextjsamplify-dev-190351-site-service-api-80/view"
                    ]
                ]
            }
        },
        "LoadBalancerAliasDomainName": {
            "Value": {
                "Fn::GetAtt": [
                    "LoadBalancer",
                    "DNSName"
                ]
            }
        },
        "LoadBalancerCnameDomainName": {
            "Value": "lb-amplify-nextjsamplify-dev-190351.wp-kyoto.net"
        },
        "CloudfrontDistributionAliasDomainName": {
            "Value": {
                "Fn::GetAtt": [
                    "Distribution",
                    "DomainName"
                ]
            }
        },
        "CloudfrontDistributionCnameDomainName": {
            "Value": "fargate-example.wp-kyoto.net"
        }
    }
}

Comment