はじめに
AWS ECS を使ったコンテナ環境をAWS マネジメントコンソールから手作業で構築し、そこに CI/CD を少しずつ足していきます。
想定している読者は次のような方です。
- VPCやEC2等の設定、構築をした事がある方。
- コンテナは触ったことがあるが、ECS はこれからという方。
- ECS Fargateを使用して、ECSの基礎知識を確認しておきたい方。
- 昔ECSを使ったが久しぶりのため、改めて作業方法の再確認をしたい方、復習をして知識を維持したい方。
EC2インスタンスにECS Agentを導入して、管理する方法は本記事では実施しません。
本記事について
- 本記事ではALBやFargateなどの有料サービスを使用するため、費用が発生します。ALBとFargateは起動している時間に応じた課金のため、数時間で完了すれば数USドル程度ですみますが、削除し忘れて1か月放置すると40USドル程度になります。検証後は必ずリソースの削除をお願いします(手順は記事末尾に記載しています)。
- AWSマネジメントコンソールの画面構成は、随時更新されます。本記事は2026年9月時点の画面をもとにしています。項目名や配置が異なる場合は、適宜読み替えてください。
- 掲載している手順は、実際にAWS上で検証した内容です。ただし、学習しやすさを優先してセキュリティ設定を簡略化している箇所があります(タスクをパブリックサブネットに配置する、セキュリティグループで広い範囲からのアクセスを許可する、など)。そのため、検証用のAWSアカウントでの実施を推奨します。
- 本記事では、GitHubとAWSを連携させる設定(OIDCのIDプロバイダー、GitHubアプリの連携など)を行います。これらは他のプロジェクトと共有される設定のため、検証後に削除する際はご注意ください。同じアカウントやGitHub組織で他にも利用しているものがあると、そちらの連携まで止まってしまう可能性があります。削除対象かどうか分からない場合は、無理に消さずに残しておくほうが安全です(残しておいても費用は発生しません)。
- 共用アカウントや業務で使用しているアカウントで実施する場合は、組織のルールを確認したうえで設定内容を見直してから、本記事を参考にしたり、手順の実施をしてください。
テーマとゴール
いくつかテーマと、ゴールを決めて進めていきます。内容を1つのブログにすると長くなるため、複数回に分けています。
| 第何回 | テーマ | ゴール |
| 第1回 | コンテナと ECS の基礎、手動構築 | ブラウザに「Hello from Amazon ECS」 が表示される |
| 第2回 | 手動リリースと CodePipeline による自動リリース | ECR への push でローリングアップデート |
| 第3回 | ECS ネイティブ Blue/Green デプロイ | テストリスナーで確認してから本番切り替え |
| 第4回 | GitHub Actions で ECR への push を自動化 |
“git push”だけでリリースまで完了する |
| 第5回 | プライベートサブネットを使用した構成 |
プライベートサブネットとエンドポイントを利用した環境構築の検証をする |
今回の記事の構成は以下のようになります。
この記事ではNATゲートウェイを使用しません。 タスクをパブリックサブネットに置いて、パブリックIPを割り当ててECRからイメージを取得します。パブリックサブネットを使用している理由としては、NATゲートウェイは単体で月45USドル前後かかるので学習用としてはコストが高いことや、学習用の構成として単純化するためです。実際の業務では、プライベートサブネットに配置しエンドポイントを使用します(途中でその話に触れます)。正確な金額は、AWS公式ページにてご確認ください。
プライベートサブネットに配置した構成については、第5回の記事に記載いたします。

概念の整理
ECSの作業に取り組む前に、用語の確認をします。
- クラスター(Cluster)
- タスクを動かす場所の論理的なグループです。Fargate を使う場合、クラスター自体は「入れ物」で、実体としてのサーバーはありません。
- タスク定義(Task Definition)
- コンテナの設計図です。「どのイメージを、CPUとメモリでサイズ、どのポートを開けて動かすか」を書いた JSONファイルと思ってください。
- タスク定義の考慮事項
-
イミュータブル(変更不可):一度作った定義は書き換えられません。
- リビジョンの増加:修正すると、ecs-handson-task:1 、ecs-handson-task:2と番号が振られます。
-
- タスク定義の考慮事項
- コンテナの設計図です。「どのイメージを、CPUとメモリでサイズ、どのポートを開けて動かすか」を書いた JSONファイルと思ってください。
- タスク(Task)
- タスク定義から起動された、実行中の実体です。設計図が「タスク定義」、そこから生まれた個体が「タスク」という関係です。
- サービス(Service)
- 「タスクを常に N 個動かし続ける」役割を持ちます。タスクが落ちたら起動し直し、ALB への登録・解除も行います。
- Fargate
- コンテナを動かすサーバーを AWS が管理します。利用者は、CPUとメモリの組み合わせを指定して使用します。EC2インスタンスや、ECSエージェントも見ることができません。
- EC2起動タイプ
- EC2インスタンスを用意し、ECS エージェントを入れてクラスターに参加させます。インスタンスの管理が必要になる代わりに、GPU や特殊なカーネル設定など、Fargate では選べない構成が使えます。本記事では扱いません。
環境構築
実際に、学習用の環境を作成します。
1. VPCの作成
VPC コンソールの「VPCを作成」から、ウィザードを使います。
-
-
VPCコンソール → 左メニュー「お使いのVPC」→「VPCを作成」
-
「作成するリソース」で「VPCなど」を選択
-
以下の内容で設定します。
VPC: ecs-handson-vpc
| 項目 | 設定値 |
| 名前タグの自動生成 | ecs-handson |
| IPv4 CIDR ブロック | 10.0.0.0/16 |
| IPv6 CIDR ブロック | IPv6 CIDR ブロックなし |
| テナンシー | デフォルト |
| アベイラビリティーゾーン (AZ) の数 | 2 |
| パブリックサブネットの数 | 2 |
| プライベートサブネットの数 | 0 |
| NAT ゲートウェイ | なし |
| VPC エンドポイント | なし |
| DNS ホスト名を有効化 | オン |
| DNS 解決を有効化 | オン |
ALBを作成するため、サブネットを2つにしています。
右側にプレビュー図が表示されるので、パブリックサブネットが 2 つの AZ に分かれていることを確認してから「VPC を作成」ボタンを押します。
2. セキュリティグループの作成
ALB 用とタスク用で2つのセキュリティグループを作成し、タスク用のセキュリティグループは「ALB からの通信のみ」を許可します。
EC2 コンソール → 左メニュー「セキュリティグループ」→「セキュリティグループを作成」
①ALB用: ecs-handson-alb-sg
| 項目 | 設定値 |
| セキュリティグループ名 | ecs-handson-alb-sg |
| 説明 | ALB for ECS handson |
| VPC | ecs-handson-vpc |
ecs-handson-alb-sg インバウンドルール
| タイプ | プロトコル | ポート | ソース |
| HTTP | TCP | 80 | 0.0.0.0/0 |
ecs-handson-alb-sg アウトバウンドルール
-
- デフォルト(すべて許可)のままで問題ありません。
②タスク用 : ecs-handson-task-sg
| 項目 | 設定値 |
| セキュリティグループ名 | ecs-handson-task-sg |
| 説明 | ECS task for handson |
| VPC | ecs-handson-vpc |
ecs-handson-task-sg インバウンドルール
| タイプ | プロトコル | ポート | ソース |
| HTTP | TCP | 80 | ecs-handson-alb-sg |
ソース欄でCIDRではなくセキュリティグループを選択してください。検索ボックスにecs-handson-alb-sgと入力すると候補に出ます
コラム:本番構成ではどうするか
本番環境では通常、次のパターンのどちらかを採ります。
-
パターン A:プライベートサブネット + NAT ゲートウェイ
タスクをプライベートサブネットに置き、NAT経由でインターネットに出てECR からイメージを取得します。構成はシンプルですが、NAT ゲートウェイの固定費とデータ処理料金がかかります。 -
パターン B:プライベートサブネット + VPC エンドポイント
NATを使わず、必要なAWS サービスへのVPC エンドポイントを個別に用意します。ECS on Fargate の場合以下の一覧にあるエンドポイントは必要です。
通信量が多ければ B、構成をシンプルに保ちたければ Aという形になります。業務要件に応じて設計をします。
また、使用するエンドポイントは以下になります。今回は学習用のため、コスト優先でタスクをパブリックサブネットに置き、パブリック IP を付ける構成としているため作成しませんが、業務で使用する場合はエンドポイントを使用した構成を検討します。
| エンドポイント | 種類 | 用途 |
| com.amazonaws.ap-northeast-1.ecr.api | Interface | ECR の API 呼び出し |
| com.amazonaws.ap-northeast-1.ecr.dkr | Interface | イメージレイヤーの取得 |
| com.amazonaws.ap-northeast-1.s3 | Gateway | ECR のレイヤー実体はS3にあるため使用 |
| com.amazonaws.ap-northeast-1.logs | Interface | CloudWatch Logsへの出力 |
3.ECRにコンテナイメージを置く
3-1. ECR リポジトリを作成する
ECR コンソール → 「リポジトリ」→「リポジトリを作成」
| 項目 | 値 |
| リポジトリ名 | ecs-handson-nginx |
| イメージタグのミュータビリティ |
ミュータブル(可変) |
| 暗号化設定 | デフォルト(AES-256) |
ミュータブルにする理由:本記事では、「latest」タグを何度も上書きします。イミュータブル(変更不可)にすると2回目のpushが失敗するため、必ず可変にしてください。ただし、本番環境ではイミュータブル + コミット SHA タグが推奨です。「どのイメージがデプロイされているか」を後から追跡できるためです。
「イメージのスキャン設定」:デフォルトのまま(スイッチOFF)スキャンしないにの設定のままにします。 画面に「deprecated(非推奨)」と表示されます。スキャンの設定はリポジトリ単位からレジストリ単位(ECR の「プライベートレジストリ」→「スキャン設定」)に移行しました。本記事では脆弱性スキャンは扱いませんが、実運用ではレジストリ単位で基本スキャンや拡張スキャンを有効にし、フィルターで対象リポジトリを絞るのが現在の方法です。
作成すると、リポジトリ URI が表示されます。次の形式です。
123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/ecs-handson-nginx
先頭の 12 桁がご自身の AWS アカウント ID です。後で使うのでメモしておいてください。

3-2. CloudShell でイメージをビルド
AWS CloudShell にはDockerが組み込まれているため、それを利用します。個別に自身のPC上にセットアップは不要です。
マネジメントコンソール右上のターミナルアイコンからCloudShellを起動します。リージョンが東京になっていることを確認してください。
まず作業ディレクトリと HTML を作ります。CloudShell上で実行してください。
mkdir -p ~/ecs-handson && cd ~/ecs-handson
cat > index.html <<'EOF'
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>ECS ハンズオン</title>
</head>
<body style="font-family:sans-serif;text-align:center;padding-top:80px">
<h1>Hello from Amazon ECS</h1>
<p>version: <strong>v1</strong></p>
</body>
</html>
EOF
次に Dockerfile を作ります。
cat > Dockerfile <<'EOF'
FROM public.ecr.aws/nginx/nginx:1.27-alpine
COPY index.html /usr/share/nginx/html/index.html
EOF
ベースイメージにpublic.ecr.awsを使う理由:Docker Hubのnginxは匿名pullのレート制限に引っかかることがあります。ECR Public GalleryにはAWSがミラーしたNginxイメージがあり、この制限を避けやすくなります。CodeBuildやGitHub Actionsからビルドするときも同じ理由でECR Publicを選ぶと制限を避けやすくなります。
なお ECR Public にもプル制限はありますが、public.ecr.awsにdocker loginしてから取得すれば上限が緩和されます。今回の規模であれば、匿名のままでも問題になりません
3-3. ECR にログインして push する
CloudShell はログイン中の IAM 権限をそのまま使えるので、認証情報の設定は不要になります。
以下のコマンドをCloudShellで実行します。
AWS_REGION=ap-northeast-1
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REPO=ecs-handson-nginx
REGISTRY=${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com
ECR_URI=${REGISTRY}/${REPO}
echo $ECR_URI
表示された URI が、先ほどコンソールで見たものと一致していることを確認します。
以下のコマンドを実行し、ECR にログインします。Login Succeededが表示されることを確認します。
aws ecr get-login-password --region ${AWS_REGION} \
| docker login --username AWS --password-stdin ${REGISTRY}

ビルドして、タグを2つ付けてpushします。
docker build -t ${REPO}:v1 .
docker tag ${REPO}:v1 ${ECR_URI}:v1
docker tag ${REPO}:v1 ${ECR_URI}:latest
docker push ${ECR_URI}:v1
docker push ${ECR_URI}:latest
v1とlatestの両方のタグをつける理由:v1はどのバージョンかを人間が識別するため、latestは次回移行で構築予定のCodePipelineがイメージの更新を検知するためのタグです。CodePipelineのECRソースアクションは特定のタグ 1 つだけを監視するので、v2、v3と増やしていくだけでは検知されないためです。
なお、CloudShellのホームディレクトリは1GB までです。今回のAlpineベースのイメージは数十 MB なので問題ありませんが、大きなイメージを扱うときはdocker system prune で掃除してください
ECR コンソールに戻り、ecs-handson-nginxリポジトリにv1とlatestの2つのタグ(実体は同じイメージ)が入っていることを確認します。

4.ALBの設定
4-1. ターゲットグループを作成
EC2 コンソール → 左メニュー「ターゲットグループ」→「ターゲットグループの作成」
ターゲットタイプは必ず「IP アドレス」にしてください。「インスタンス」を選ぶとFargateでは使えません。FargateタスクにはEC2インスタンスIDが存在せず、ENIに割り当てられたIPアドレスしか持たないためです。
そしてターゲットは手で登録しません。ECS サービスがタスクを起動するたびに、その IP を自動でターゲットグループに登録し、タスクが止まれば自動で解除します。この「ECSがALBの登録を代行する」という関係が、ECSと ALBの連携になります。
| 項目 | 設定値 |
| ターゲットタイプの選択 | IPアドレス |
| ターゲットグループ名 | ecs-handson-tg-blue |
| プロトコル : ポート | HTTP:80 |
| VPC | ecs-handson-vpc |
| プロトコルバージョン | HTTP1 |
※ ターゲットグループ名をecs-handson-tg-blueとしているのは、後半の回でブルーグリーンデプロイを検証するためです。
ヘルスチェック
| 項目 | 設定値 |
| ヘルスチェックプロトコル | HTTP |
| ヘルスチェックパス | / |
| 正常のしきい値 | 2 |
| 非正常のしきい値 | 2 |
| タイムアウト | 5秒 |
| 間隔 | 30秒 |
| 成功コード | 200 |
「次へ」を押すとターゲット登録の画面になりますが、何も登録せずに「ターゲットグループの作成」を押してください。

4-2. ALB を設定する
EC2 コンソール → 左メニュー「ロードバランサー」→「ロードバランサーの作成」→「Application Load Balancer」
セキュリティグループの欄には、defaultが最初から入っていることがあります。defaultを外してecs-handson-alb-sgのみに設定してください。
| 項目 | 設定値 |
| ロードバランサー名 | ecs-handson-alb |
| スキーム | インターネット向け |
| IP アドレスタイプ | IPv4 |
| VPC | ecs-handson-vpc |
| マッピング | 2つのAZ をチェックし、それぞれパブリックサブネットを選択 |
| セキュリティグループ | ecs-handson-alb-sg |
| リスナー | HTTP : 80 → 転送先 ecs-handson-tg-blue |

作成後、ALBの詳細画面に表示されるDNS名をメモします。次のような形式です。
ecs-handson-alb-1234567890.ap-northeast-1.elb.amazonaws.com

5.ECSクラスター・タスク定義・サービスの設定
5-1. クラスターを作成
ECSコンソール → 左メニュー「クラスター」→「クラスターの作成」
| 項目 | 設定値 |
| クラスター名 | ecs-handson-cluster |
| インフラストラクチャ | AWS Fargate(サーバレス) |
| Container Insights | オフ有効にすると CloudWatch の課金が発生します |
5-2. タスク定義を作成
ECSコンソール → 左メニュー「タスク定義」→「新しいタスク定義の作成」
- タスク定義の設定
| 項目 | 設定値 |
| タスク定義ファミリー | ecs-handson-task |
- インフラストラクチャの要件
- タスクロール(Task Role):コンテナの中のアプリケーションがAWS APIを使用するためのロールです。今回のNginxはAWS APIを使用しないため、設定しません。
- タスク実行ロール(Task Execution Role):ECSのインフラ側が使うロール。ECRからイメージをpullし、CloudWatch Logsにログを書くために必要になります。
| 項目 | 設定値 |
| 起動タイプ | AWS Fargate |
| OS/アーキテクチャ | Linux/X86_64 |
| ネットワークモード | awsvpc (Fargeteでは固定) |
| タスクサイズ – CPU | .25 vCPU |
| タスクサイズ – メモリ | .5 GB |
| タスクロール | なし |
| タスク実行ロール | 新しいロールの作成(ecsTaskExecutionRoleが自動生成されます) |

コンテナ – 1
| 項目 | 設定値 |
| 名前 | nginx |
| イメージ URI | <アカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/ecs-handson-nginx:latest |
| 必須コンテナ | はい |
| ポートマッピング – コンテナポート | 80 |
| ポートマッピング – プロトコル | TCP |
| ポートマッピング – アプリケーションプロトコル | HTTP |

ECRイメージを閲覧ボタンを押下します。ウィンドウが表示されるので、対象イメージを選択してください。

ログ収集
「ログ収集を使用」をオンにします。/ecs/ecs-handson-taskというロググループが自動で作られ、コンテナの標準出力がCloudWatch Logsに流れます。トラブルシュートに必須なので必ず有効にしてください。
5-3. サービスを作成する
作成したクラスター ecs-handson-clusterを開き、「サービス」タブ →「作成」の順番に選択します。
サービスの設定
| 項目 | 設定値 |
| タスク定義ファミリー | ecs-handson-task |
| タスク定義のリビジョン | 1 |
| サービス名 | ecs-handson-service |
環境
| 項目 | 設定値 |
| コンピューティングオプション | 起動タイプ |
| 起動タイプ | Fargate |
| プラットフォームバージョン | LATEST |
デプロイ設定
| 項目 | 設定値 |
| スケジューリング戦略 | レプリカ |
| 必要なタスク | 2 |
| アベイラビリティゾーンの再調整 | チェックをつける |
| ヘルスチェックの猶予期間 | デフォルト |
デプロイオプション(展開してください)
| 項目 | 設定値 |
| デプロイタイプ | ローリングアップデート |
| 最小実行タスク率 | 100 |
| 最大実行タスク率 | 200 |
| デプロイ失敗の検出 – サーキットブレーカーを使用 | チェックをつける |
| 失敗時にロールバック | チェックをつける |
他の設定項目は、デフォルト設定値にします。

ネットワーキング
| 項目 | 設定値 |
| VPC | ecs-handson-vpc |
| サブネット | 本記事は設定したパブリックサブネット2つ |
| セキュリティグループ | 既存のセキュリティグループを使用を選択し、事前に作成したecs-handson-task-sgを一覧から選択 |
| パブリックIP | 本記事では、オンに設定します。 補足:NATゲートウェイまたは、エンドポイントを作成してその経路を経由する場合ははオフにします。 |

ロードバランシング
| 項目 | 設定値 |
| ロードバランシングを使用 | チェックをつける |
| VPC | ecs-handson-vpc |
| ロードバランサーの種類 | Application Load Balancer |
| コンテナ | nginx 80:80 |
| Application Load Balancer | 既存のロードバランサーを使用を選択 |
| ロードバランサー | ecs-handson-alb |
| リスナー | 既存のリスナーを使用 → リスナーはHTTP:80を選択します |
| ターゲットグループ | 既存のターゲットグループを使用 → ターゲットグループはecs-handson-tg-blueを選択します。 |
| ヘルスチェックパス | / |
| ヘルスチェックプロトコル | HTTP |

設定したら作成ボタンを押下します。デプロイ完了まで、2~5分程度待ちます。
6.動作確認
ECSの画面で、クラスターの「タスク」タブを開くと、2 つのタスクが表示されます。ステータスが「実行中」になることを確認します。
ターゲットグループを見る
EC2 コンソール → ターゲットグループ → ecs-handson-tg-blue → 「ターゲット」タブ
ターゲットグループにターゲットを手動で設定していませんが、何も登録していないのに2 つの IP アドレスのヘルスステータスが、Healthyで存在している状態になります。ECS サービスが自動で登録してくれた結果を確認できます。

ブラウザで確認
ALB の DNS 名をブラウザで開きます。1234567890には、設定した際に生成された数値がはいります。
http://ecs-handson-alb-1234567890.ap-northeast-1.elb.amazonaws.com
実際に接続して、「Hello from Amazon ECS」と「version: v1」が表示されれば成功です。
-
ブラウザ表示確認時は、以下の点に注意してください。
- 先頭にhttpと明示して入力するようにしてください。
最近の Chrome / Edge / Safari は、アドレスバーに入力したホスト名を自動で
https://に書き換えます。今回の ALB は HTTP:80 のリスナーしか持たないため、HTTPS で接続しようとすると応答がなくタイムアウトします。 -
タスクもターゲットも正常なのに「繋がらない」と感じたら、まず先頭がhttpとなっているかを疑ってください。それでも HTTPS に昇格される場合は、シークレットウィンドウで試すか、CloudShell からcurl -I http://<ALBのDNS名> で確認します。
- アドレスバーに「保護されていない通信」と表示されますが、HTTP で接続しているので正常です。HTTPS 化には独自ドメインと ACM 証明書が必要になるため、本ブログでは扱いません。
- 先頭にhttpと明示して入力するようにしてください。

まとめ
以下の点を確認しました。
- タスク定義はイミュータブルで、リビジョンが増えていく → 次回のリリース作業でリビジョン増加確認をします。
- サービスがタスクの数を維持し、ALBへの登録も代行する → ターゲットを手で登録せずに実施しました。実際にはECSにより登録されていることを確認しています。
- FargateではターゲットタイプがIPアドレスになる → インスタンスIDが存在しないためです。
- タスク実行ロールとタスクロールは役割が違う → それぞれのロールの使用目的が違います。
- タスク実行ロール:ECS のインフラ側が使うロール
- タスクロール : コンテナの中のアプリケーションがAWS APIを実行するためのロール
続き
次回は、手動リリースとCodePipelineを使用したリリースを確認します。(第2回へ移動する)
最後に
ここで作成したリソースを削除する場合は、以下のリソースを削除してください。次の回に進む場合は、同じリソースを使用しますので削除せずに進みます。
- ECS サービス … ecs-handson-service を削除(タスクも一緒に消えます)
- ECS クラスター … ecs-handson-cluster を削除
- ALB … ecs-handson-alb を削除
- ターゲットグループ … ecs-handson-tg-blue を削除(ALB 削除後でないと消せません)
- ECR リポジトリ … ecs-handson-nginx を削除(イメージが入っていても強制削除できます)
- CloudWatch ロググループ … /ecs/ecs-handson-task を削除
- VPC … ecs-handson-vpc を削除
-
VPC が削除できないとき:ENI(ネットワークインターフェース)が残っている可能性があります。ALB を削除した直後は ENI の解放に数分かかることがあるので、少し待ってから再試行してください。それでも消えない場合は、EC2 コンソールの「ネットワークインターフェース」でecs-handson-vpcに紐づくENI を探し、手動で削除する必要があります。
-