
CDK synthテンプレートでECSアプリをAWSコンソールからデプロイしてみた ― つまずきポイントと解決策まとめ
はじめに
「社内で運用しているECSアプリを、自分の個人AWSアカウントにもデプロイしたい」——そんな動機でデプロイ作業を始めたところ、多くのつまずきポイントがありました。
CDK bootstrapを省略してCloudFormationコンソールから直接デプロイする方法、ECRのクロスアカウントアクセス、DNS設定、Cognito認証の構成まで、一連の作業で遭遇したトラブルとその解決策をまとめます。
前提・環境
- デプロイ対象: AI Starter
- IaC: AWS CDK(TypeScript)
- コンテナイメージ: 別のAWSアカウント(ソースアカウント)のECRリポジトリに保管
- デプロイ先: 個人AWSアカウント
- リージョン: ap-northeast-1
全体の流れ

事前準備: VPC・ACM証明書・ドメインの用意
CDKスタックはVPCやACM証明書を自前で作成せず、既存リソースのIDをパラメータとして受け取る設計になっています。そのため、CloudFormationスタックをデプロイする前に以下のリソースを手動で用意する必要があります。
VPCの作成
AWSコンソールの「VPC」から新規作成します。今回はテスト用途なので「VPC など」を選択し、以下の構成で作成しました。
| 項目 | 設定値 |
|---|---|
| 名前タグ | 任意(例: ai-starter) |
| IPv4 CIDRブロック | 10.0.0.0/16 |
| アベイラビリティーゾーン数 | 2 |
| パブリックサブネット数 | 2 |
| プライベートサブネット数 | 2 |
| NATゲートウェイ | なし(コスト削減のため、Publicモードで利用) |

作成後、CloudFormationのパラメータに入力するため、以下のIDをメモしておきます。

- VPC ID(例:
vpc-0abc1234def56789) - パブリックサブネットID × 2(例:
subnet-0abc...,subnet-0def...) - プライベートサブネットID × 2(同上)
ドメインの取得
Route 53で独自ドメインを取得します。.clickなどの安価なTLD(トップレベルドメイン)を選べば、年間数ドルで済みます。ドメインの登録は数分〜数十分で完了します。

ドメインを登録します。

.click は 3ドル/年 で一番安かったです。

チェックアウトします。
ACM証明書の作成
ALBでHTTPSを有効にするため、AWS Certificate Manager(ACM)で証明書を作成します。Route 53でドメインを管理していれば、DNS検証で「Route 53でレコードを作成」ボタンをクリックするだけで検証が完了し、証明書が発行されます。

証明書をリクエストします。

パブリック証明書をリクエストします。


先ほど登録したドメイン + サブドメインを登録します。今回の場合 stg でサブドメインを登録しました。

「Route 53 でレコードを作成」 を選択します。

証明書のARNに含まれるID部分を、CloudFormationのパラメータCertificateIdに使用します。

CDK synthテンプレートをCloudFormationコンソールからデプロイする
1. テンプレートの生成
pnpm cdk synth > template.yaml
2. CloudFormationコンソールでスタックを作成
AWSコンソールで「CloudFormation」を開き、以下の手順でスタックを作成します。
-
「スタックの作成」→「新しいリソースを使用(標準)」を選択

-
「テンプレートファイルのアップロード」で、先ほど生成した
template.yamlをアップロード

-
スタック名を入力(例:
cmais-prd-ai-starter-stack)

3. パラメータの入力
事前準備で用意したリソースのIDを入力します。
| パラメータ | 入力する値 |
|---|---|
| VpcId | VPC ID(例: vpc-0abc1234def56789) |
| LBSubnetIds | パブリックサブネットID1 / 2 |
| ServiceSubnetIds | パブリックサブネットID1 / 2 |
| CertificateId | ACM証明書のID(ARNの末尾部分) |
| NetworkType | Public(NATゲートウェイなしの場合)または Private |
NetworkTypeは、ALBをパブリックサブネットに配置してインターネットから直接アクセスする場合はPublic、プライベートサブネットに配置する場合はPrivateを選択します。テスト用途でNATゲートウェイを作成していない場合はPublicを選びましょう。
4. スタック作成の実行
残りのオプションはデフォルトのまま「次へ」を進め、最後に「AWS CloudFormation によって IAM リソースが作成される場合があることを承認します。」にチェックを入れて「送信」をクリックします。

スタックの作成には10〜15分程度かかります。ステータスがCREATE_COMPLETEになれば成功です。
スタック作成に失敗した場合
スタック作成に失敗した場合(ROLLBACK_COMPLETEやCREATE_FAILED)、そのスタックを削除してから再作成する必要があります。パラメータを修正して同じスタック名で再度作成してください。
ecr:SetRepositoryPolicyの権限が必要です。権限が不足した場合は、組織のセキュリティ管理者に相談しましょう。
ECRとコンテナイメージpullの仕組み
ここで「レジストリ」「イメージ」「アーティファクト」といった用語を整理しておきます。
| 用語 | 意味 |
|---|---|
| コンテナイメージ | アプリケーション + ランタイムをパッケージ化したもの(zipのようなイメージ) |
| レジストリ | イメージを保管・配布するサービス(Docker Hub、GitHub Container Registry、ECRなど) |
| リポジトリ | レジストリ内のイメージ名前空間(GitHubリポジトリに相当) |
| アーティファクト | ビルド成果物の総称。コンテナイメージはアーティファクトの一種 |
Amazon ECR(Elastic Container Registry) は、AWSが提供するマネージドコンテナレジストリです。GitHubがソースコードを管理するように、ECRはコンテナイメージを管理します。ECSタスクは起動時にECRからdocker pullでイメージを取得し、そのイメージからコンテナを起動します。
2層のアクセス制御
ECRへのアクセスは以下の2層で制御されています。
-
IAM Task Execution Role(pull側): ECSタスクに付与されるIAMロール。
ecr:BatchGetImageやecr:GetDownloadUrlForLayerなどの権限が必要です。CloudFormationスタックが自動で作成するため、通常は意識する必要がありません。 -
ECR Resource-based Policy(レジストリ側): ECRリポジトリ自体に設定するポリシー。どのAWSアカウントがpullできるかを制御します。クロスアカウントの場合、ここにデプロイ先アカウントのIDを明示的に追加する必要があります。

今回のケースでは、IAMロール(1層目)はCloudFormationが自動作成済みでしたが、ECRリポジトリのResource-based Policy(2層目)に個人アカウントIDが未登録だったため、イメージのpullが拒否されました。
DNS・ALB・HTTPS設定
DNSレコードとは
DNSレコードとは、ドメイン名とその送り先を紐付ける「対応表のエントリ」です。すべてのドメインにはネームサーバー(DNSサーバー)があり、この対応表を管理しています。Route 53はAWSのネームサーバーですが、他のサービス(Cloudflare、GoDaddyなど)でドメインを管理している場合は、そちらのDNS設定画面で同様のレコードを作成します。
主要なレコードタイプ:
| レコードタイプ | 紐付け先 | 用途 |
|---|---|---|
| A | IPアドレス | stg.example.click → 203.0.113.50 |
| CNAME | 別のホスト名 | stg.example.click → my-alb-123.elb.amazonaws.com |
| AAAA | IPv6アドレス | Aレコードと同じ役割のIPv6版 |
| MX | メールサーバー | メールの送信先を指定 |
| TXT | 任意のテキスト | ドメイン検証(ACMのDNS検証など)に使用 |
今回はALBへの接続なので、より効率的なAレコード(エイリアス)を使用しています。
ALBのDNSレコード設定
CloudFormationスタック作成後、ALBは作成されますがDNSレコードは自動で作られません。Route 53で**Aレコード(エイリアス)**をALBに向けて手動で作成する必要があります。
-
スタック > スタック詳細から LoadBalancerDNS の値をメモします。

-
Route 53 > 先ほど登録したドメイン > 「レコードを作成」を選択します。

-
レコードのパラメータを入力します。
| パラメータ | 入力する値 |
|---|---|
| レコード名 | stg(前指定したサブドメイン) |
| レコードタイプ | A |
| トラフィックのルーティング先 | Application Load Balancer と Classic Load Balancer へのエイリアス |
| ロードバランサー | dualstack.<前メモした LoadBalancerDNS の値> |

- 「レコードを作成」を選択
これで設定したドメインからアプリケーションにアクセスできるようになります。
Cognito + Secrets Managerの設定

問題: すべてのルートで404が返る
デプロイ後、/healthcheckは正常に200を返すのに、/loginや/など他のルートがすべて404になる現象が発生しました。
原因の調査
アプリケーションのコードを調査したところ、サーバーは起動時にSecrets Managerからシークレットを読み込み、OIDC認証の設定を初期化します。この初期化に失敗すると、サーバーはヘルスチェック専用モードにフォールバックし、/healthcheck以外のルートは登録されません。
具体的には、auth-configシークレットのissuerフィールドが空文字列だったため、openid-clientライブラリがTypeError: Cannot read properties of nullを投げていました。
Cognitoユーザープールの作成
OIDC認証のためにCognitoユーザープールを作成しました。
-
Amazon Cognito > ユーザプールを作成

-
アプリケーションを設定します。

-
リターンURLを設定します。
| 項目 | 設定値 |
|---|---|
| アプリケーションタイプ | 従来のウェブアプリケーション |
| アプリケーション名 | 任意 |
| サインインオプション | Eメール |
| リターンURL | https://YOUR_DOMAIN/auth/callback |
| 許可されたスコープ | email, openid, profile |

ユーザの登録
-
Amazon Cognito > ユーザプール > ユーザーを作成

-
ユーザー(自分)宛に招待します。
| 項目 | 設定値 |
|---|---|
| 招待メッセージ | Eメールで招待を送信 |
| E メールアドレス | <自身のE メールアドレス> |
| サインインオプション | Eメール |
| 仮パスワード | パスワードの生成 |

- 仮パスワードを受信します。

scopeの設定ミス
Cognitoのアプリクライアント作成時、デフォルトのスコープにphoneが含まれていました。アプリケーションが要求するスコープ(email openid profile)と一致しないため、ログイン時にinvalid_scopeエラーが発生しました。
aws cognito-idp update-user-pool-client \
--user-pool-id ap-northeast-1_XXXXXXX \
--client-id YOUR_CLIENT_ID \
--allowed-o-auth-scopes email openid profile \
--allowed-o-auth-flows code \
--allowed-o-auth-flows-user-pool-client \
--supported-identity-providers COGNITO \
--callback-urls '["https://YOUR_DOMAIN/auth/callback"]'
Secrets Managerの設定
アプリケーションが参照する主要なシークレット:
| シークレット名 | 必須フィールド | 説明 |
|---|---|---|
auth-config |
issuer, client_id, client_secret |
OIDC認証設定 |
aws |
bedrock_region |
Bedrock利用リージョン |
customize |
title |
アプリタイトル等のカスタマイズ |
auth-configのissuerフィールドには、Cognitoユーザープールのissuer URLを設定します:
https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_XXXXXXX
管理画面でアシスタントを有効化する
ここまで来れば、ログインできるようになります。

メールを入力します。

仮パスワードを入力します。

新しいパスワードを設定します。
問題: ログイン後「現在設定中のため、しばらくしてからブラウザをリロードしてください」と表示される
Cognito認証が通りログインできるようになっても、アプリのトップページに「設定中」メッセージが表示されて先に進めませんでした。

原因
このアプリケーションでは、利用可能なAIアシスタント(モデル)が1つも有効になっていない場合、チャット画面に遷移できず「設定中」のフォールバック画面が表示されます。デフォルトではすべてのアシスタントが無効(enabled: false)になっており、管理画面から手動で有効化する必要があります。
解決手順
1. DynamoDBでユーザーに管理者権限を付与
管理画面にアクセスするには、ユーザーテーブルで自分のユーザーのgroupをAIS:adminに設定する必要があります。
aws dynamodb put-item \
--table-name YOUR_PREFIX-user \
--item '{"id":{"S":"your-email@example.com"},"group":{"S":"AIS:admin"}}'
2. 管理画面でアシスタントを有効化
https://YOUR_DOMAIN/admin/assistantsにアクセスし、使用したいモデル(例: Claude Sonnet 4.6)を有効にします。

管理画面から「アシスタント」を選択します。

任意モデル(例:Claude Sonnet 4.6)を有効化します。
3. ブラウザをリロード
アシスタントを有効化したら、メインアプリのページをリロードするとチャット画面に遷移できます。

つまずきポイント一覧
| 問題 | 原因 | 解決策 |
|---|---|---|
| CloudFormationテンプレートのパースエラー | pnpm stdout混入 | テンプレート先頭の余計な行を削除 |
| サブネットID不正 | 名前をIDと間違えた | VPCコンソールからIDをコピー |
| ECSデプロイ失敗(Circuit Breaker) | ECRクロスアカウントアクセス拒否 | ターゲットアカウントリストに追加 |
| DNS解決できない | Route 53にレコード未作成 | AレコードをALBエイリアスで作成 |
| HTTP接続不可 | ALBにHTTPリスナーなし | HTTPSでアクセス |
| 全ルート404 | Secrets Manager設定不備 | auth-configのissuerを正しく設定 |
| ログイン時invalid_scope | Cognitoスコープ設定ミス | phoneを除外しprofileを追加 |
| ログイン後「設定中」表示 | アシスタント未有効化 | 管理画面(/admin/assistants)で有効化 |
/adminで404 |
ルートページなし | /admin/を指定 |
まとめ
CDK synthテンプレートからのCloudFormationデプロイは、CDK bootstrapを省略できる反面、パラメータ入力やテンプレートの手動修正が必要です。
今回の経験で学んだポイント:
- ECRクロスアカウントアクセスはリソースベースポリシーで制御されるため、デプロイ先アカウントの事前登録が必要
- ECSのCircuit Breakerはイメージpull失敗時にも発動するため、まずECRアクセスを疑う
- ヘルスチェック以外404のパターンは、アプリケーション起動時の設定読み込みエラーが原因の可能性がある
- Cognitoのスコープ設定はデフォルト値に注意。アプリが要求するスコープと一致させる
- アシスタントの有効化はインフラとは別のアプリケーション設定。DynamoDBで管理者権限を付与し、管理画面から操作する
特に、CloudFormationスタック自体は正常に作成されても、アプリケーションレベルの設定(Secrets Manager、Cognito、アシスタント有効化)が正しくないと動作しないケースは見落としがちです。インフラ構築とアプリケーション設定を分けて検証することをおすすめします。



