はじめに
この連載では、AWS ECS を使ったコンテナ環境を AWS マネジメントコンソールから手作業で 構築し、そこに CI/CD を少しずつ足していきます。
想定している読者は次のような方です。
- VPCやEC2等の設定、構築をした事がある方。
- コンテナは触ったことがあるが、ECS はこれからという方。
- ECS Fargateを使用して、ECSの基礎知識を確認しておきたい方。
- 昔ECSを使ったが久しぶりのため、改めて作業方法の再確認をしたい方、復習をして知識を維持したい方。
前回について
本ブログは第4回目となります。前回(第3回へ移動する)は、CodePipelineでECRへのpushを検知し、ECSでBlue / Green デプロイまで実施しました。
本記事について
- 本記事では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回 | プライベートサブネットを使用した構成 |
プライベートサブネットとエンドポイントを利用した環境構築の検証をする |
第4回目のゴール
概要イメージになります。

- GitHub のリポジトリでファイルを編集してコミットする(GitHubの作業はすべてブラウザ上で実施します。数多くのファイルを今回は扱いませんので、この形式で作業をすすめます。)
- GitHub Actions が自動でイメージをビルドし、ECR に push する
- CodePipeline が起動し、Blue/Green デプロイが一時停止する
- テストリスナーで確認して承認すると、本番に反映される
-
画面に表示されたコミットID から、ECR のイメージ、タスク定義まで追跡できる
準備
- 第3回までのリソースが残っていること(Blue/Green 設定済みの ECS サービス、CodePipeline)
- GitHub のアカウントがあること
1.GitHub リポジトリを用意する
1-1. リポジトリの作成
GitHub にログインし、右上の「+」→「New repository」
| 項目 | 設定値 |
| Repository name | ecs-handson-app |
| Visibility | Private |
| Add a README file | チェックを入れる |
「Create repository」ボタンを押下します。
1-2. リポジトリの ID を調べる
注:2026年7月15日以降に作成された GitHub リポジトリでは、GitHub が発行する OIDC トークンの中身(subクレーム)の形式が変わりました。(参考:クラスメソッド Github ActionsのIDトークンでsubクレームの形式が変わってた)
API で情報を取るために、リポジトリの基本情報だけを読める、有効期限の短いトークンを発行します。
GitHub 右上のアイコン →「Settings」→ 左メニュー最下部の「Developer settings」→「Personal access tokens」→「Fine-grained tokens」→「New fine-grained personal access token」

| 項目 | 設定値 |
| Token name | ecs-handson-repo-id(任意) |
| Expiration | 7 days(ID を調べたら不要になるため短くします) |
| Repository access | Only select repositories → ecs-handson-appを選択 |
「Permissions」欄で「+ Add permissions」を押し、一覧から「Metadata」を選びます。アクセスレベルが Read-only になっていることを確認してください。「Repositories 0」の表示が「Repositories 1」に変われば設定できています。他の権限は追加しません。
「Generate token」を押すと、github_pat_で始まるトークンが表示されます。この画面を離れると二度と表示されないので、すぐにコピーして次のコマンドを実行します。(コピーした内容は、紛失や第3者に見られないようにご注意ください。)

GH_TOKEN=<コピーしたトークン>
curl -s -H “Authorization: Bearer ${GH_TOKEN}” \
https://api.github.com/repos/${GH_OWNER}/${GH_REPO} \
| jq ‘{owner: .owner.login, owner_id: .owner.id, repo: .name, repo_id: .id}’
ID を確認できたら、トークンは Fine-grained tokens の一覧から削除してください。
以下のような結果が返ってきます。
{
"owner": "Your ID",
"owner_id": 12345678,
"repo": "ecs-handson-app",
"repo_id": 123456789
}
owner_idとrepo_idのメモを控えておきます。
以前から持っているリポジトリを使う場合は、ID の付かない従来の形式のままです(オプトインしている場合や、2026年7月15日以降に名前を変えた場合を除く)。その場合は 3-3 の信頼ポリシーで、ID の付かない形式を使ってください。
2.デプロイ準備
2-1.テスト用ファイル配置準備
GitHubのリポジトリ画面で「Add file」→「Create new file」を選択します。(自分のPCからVisualStudioCode等で編集し、gitコマンドでリポジトリへpushしていただいても問題ありません。その際は、記事の内容を読み替えてください。本記事では、gitコマンドについて説明しません。)

2-2.アプリのファイルを置く
ブラウザ上で「index.html」と「Docker」の2ファイルを作ります。
①index.html
<!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;background:#e6f7f7">
<h1>Hello from Amazon ECS</h1>
<p>version: <strong>v6</strong>(GitHub Actions でビルド)</p>
<p style="color:#666">commit: <code>__GIT_SHA__</code></p>
</body>
</html>
入力したら右上の「Commit changes…」ボタンを押下→ Commit changes が表示されるので、Commit messagesに Create index.html等のメッセージを入力 → 右下の Commit changesボタンを押下します。
続けて同様にGitHubのリポジトリ画面で「Add file」→「Create new file」を選択し、Dockerfileを作成します。内容は以下のようにします。
② Dockerfile
FROM public.ecr.aws/nginx/nginx:1.27-alpine
ARG GIT_SHA=local
COPY index.html /usr/share/nginx/html/index.html
RUN sed -i “s/GIT_SHA/${GIT_SHA}/” /usr/share/nginx/html/index.html
入力したら右上の「Commit changes…」ボタンを押下→ Commit changes が表示されるので、Commit messagesに Create Docker file 等のメッセージを入力 → 右下の Commit changesボタンを押下します。
「__GIT_SHA__」という目印を、ビルド時にコミットIDで置き換えています。このようにすると、画面を見るていると、どのコミットからビルドされたイメージが動いているかが分かるようになります。
リポジトリに、index.htmlファイルとDockerファイルがあることを確認します。
3.AWS 側:GitHub を信頼する設定
3-1. IDプロバイダー
AWSコンソールに戻ります。
IAM コンソール → 左メニュー「ID プロバイダー」→「プロバイダーを追加」
| 項目 | 設定値 |
| プロバイダーのタイプ | OpenID Connect |
| プロバイダーの URL | https://token.actions.githubusercontent.com |
| 対象者(Audience) | sts.amazonaws.com |
IDプロバイダーはアカウントに1つ:同じURLのIDプロバイダーは1つしか作れません。すでに他の用途で作成してある場合は、それをそのまま使用してください。

3-2. GitHub Actions 用の権限ポリシーを作る
先に、ロールに付ける権限を作ります。GitHub Actionsがするのは「ECRにログインして、ecs-handson-nginxにpushする」ことです。それだけを許可します。
IAM コンソール →「ポリシー」→「ポリシーの作成」→「JSON」
以下の、“123456789012”はご自身のAWSアカウントIDに置き換えてください。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "EcrLogin",
"Effect": "Allow",
"Action": "ecr:GetAuthorizationToken",
"Resource": "*"
},
{
"Sid": "EcrPush",
"Effect": "Allow",
"Action": [
"ecr:BatchCheckLayerAvailability",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:PutImage",
"ecr:BatchGetImage"
],
"Resource": "arn:aws:ecr:ap-northeast-1:123456789012:repository/ecs-handson-nginx"
}
]
}
ポリシー名は、ecs-handson-github-ecr-pushで保存します。
3-3. GitHub Actions 用の IAM ロールを作る
IAM コンソール →「ロール」→「ロールを作成」
ステップ 1:信頼されたエンティティを選択
「カスタム信頼ポリシー」を選び、以下を貼り付けます。置き換える箇所は 5 つです。
| 置き換え対象 | 置き換える設定値 |
| 123456789012 | 自身のAWSアカウントID |
| GitHubユーザー名 | 自身のGitHubユーザー名 |
| 12345678 | 1-2で調べたowner_id |
| ecs-handson-app | リポジトリ名(変えていなければそのまま) |
| 987654321 | 1-2で調べたrepo_id |
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": "repo:GitHubユーザー名@12345678/ecs-handson-app@987654321:ref:refs/heads/main"
}
}
}
]
}
subの条件は必ず付けてください。audだけでsubを絞らないと、GitHub上の誰のリポジトリからでもこのロールを使えてしまいます。OIDC設定でいちばん多い事故です。
IAM コンソールの「ウェブアイデンティティ」ウィザードについて:「信頼されたエンティティ」で「ウェブアイデンティティ」を選ぶと、GitHubの組織・リポジトリ・ブランチを入力するだけで信頼ポリシーを作ってくれる画面があります。生成される条件が新しいsubの形式に対応しているか十分ご確認ください。
以前から持っているリポジトリを使う場合 : subをIDの付かない形式にします。
例:”token.actions.githubusercontent.com:sub”: “repo:hogefuga/ecs-handson-app:ref:refs/heads/main”
ステップ 2:許可を追加
3-2で作ったポリシー「ecs-handson-github-ecr-push」を選びます。
ステップ 3:名前を付ける
ロール名を「ecs-handson-github-actions」で作成します。作成後、ロールのARNをコピーしておきます。
4.GitHub 側:ワークフローを書く
4-1. ロールの ARN を登録する
GitHubの画面に移動します。
GitHubのリポジトリ →「Settings」→「Secrets and variables」→「Actions」→「Variables」タブ →「New repository variable」

| 項目 | 設定値 |
| Name | AWS_ROLE_ARN |
| Value | 3-3でコピーしたロールのARN |
Secrets ではなく Variables に登録する理由:ロールのARNを知っていても、信頼ポリシーの条件(このリポジトリのmainブランチ)を満たさなければロールは使えないからです。OIDCの設計では、GitHub側に置くものに秘密情報はありません。
4-2. ワークフローを作る
リポジトリ画面で「Add file」→「Create new file」。ファイル名に次のパスを入力します( / を入力すると自動でフォルダーになります)。
入力パス : github/workflows/build-and-push.yml
中身は以下のとおりです。
name: Build and push to ECR
on:
push:
branches: [main]
workflow_dispatch:
permissions:
id-token: write # OIDC トークンを発行するために必須
contents: read # リポジトリをチェックアウトするため
env:
AWS_REGION: ap-northeast-1
ECR_REPOSITORY: ecs-handson-nginx
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: ${{ vars.AWS_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}
- name: Login to Amazon ECR
id: ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Build, tag, and push image
env:
REGISTRY: ${{ steps.ecr.outputs.registry }}
IMAGE_TAG: ${{ github.sha }}
run: |
IMAGE=${REGISTRY}/${ECR_REPOSITORY}
docker build \
--build-arg GIT_SHA=${IMAGE_TAG::7} \
-t ${IMAGE}:${IMAGE_TAG} .
# 先にコミット ID のタグを push する
docker push ${IMAGE}:${IMAGE_TAG}
# 最後に latest を付けて push する(ここで CodePipeline が起動する)
docker tag ${IMAGE}:${IMAGE_TAG} ${IMAGE}:latest
docker push ${IMAGE}:latest
- workflow内のgithub acitonsは最新のバージョンを調べて、最新版を利用するようにお願いいたします。
- actions/checkout@v5
- aws-actions/configure-aws-credentials@v6
- aws-actions/amazon-ecr-login@v2

「Commit changes」でコミットします。
4-3. ワークフローの中身補足
- permissions: id-token: write
-
GitHub Actionsは、デフォルトではOIDC トークンを発行しません。この1行がないと、 configure-aws-credentialsは認証情報を取得できずに失敗します。
-
- タグはコミットIDにする IMAGE_TAG: ${{ github.sha }}
-
自動化する場合、コミットID をそのままタグにするのが便利です。人が番号を管理する必要がなく、タグからソースコードの該当コミットに直接たどれます。
-
- latestは最後にpushする
-
CodePipelineはlatestのpushをきっかけに起動します。先にlatestをpushすると、コミットIDのタグが付く前にパイプラインが動き出してしまうことがあります。
-
-
–build-arg GIT_SHA=${IMAGE_TAG::7}で画面にコミット ID を埋め込む
-
${IMAGE_TAG::7}は、変数の先頭 7 文字を取り出すbashの構文です( ${変数:開始位置:文字数}の開始位置を省略した形)。40 文字のコミット ID から、a1b2c3dのような短縮形を作っています。後ほどの作業において、ブラウザの画面でこのIDを確認することで、変更があったことを確認できます。
-
4-4. 最初の実行を確認する
ワークフローファイルをコミットした時点で、mainブランチへのpushとしてワークフローが自動で実行されます。リポジトリの「Actions」タブを開くと、Build and push to ECRが実行中になっています。クリックして各ステップのログを確認します。
5.CI/CDの様子確認
5-1. ECRの確認
ECR コンソール → ecs-handson-nginxを開くと、40 文字のコミットID のタグとlatestが付いたイメージが増えていることを確認します。

5-2. パイプライン
latestタグのpushを検知し、ecs-handson-pipelineが自動で起動していることを以下の流れで確認します。
- CodePipelineの画面で、Source → Build → Deploy と進むことを確認

-
ECSのデプロイタブで、Blue/Greenデプロイがテストトラフィック移行後で一時停止していることを確認

-
ブラウザで、http://<ALBのDNS名>:8080 を開くと、水色系の背景で「v6(GitHub Actions でビルド)」と commit: xxxxxxx(7文字)が表示される

-
ECSコンソールで「アクションを実行する」→ 一時停止フックでアクションを実行する→続行を選択→確認ボタンを押下
-
本番側がv6に切り替わることを、ブラウザでhttp://<ALBのDNS名>にアクセスして確認します。


5-3. もう一度、変更を流してみる
GitHubでindex.htmlを開き、鉛筆アイコンで編集します。v6をv7に、背景色を「#e6f7f7」から「#fff8d6」に変更しコミットします。5-2と同じように、もう一度確認することでGitHub ActionsとECRからパイプラインを使用した流れを確認できます。
6.コミットから本番までを追跡
「今、本番で動いているのはどのコードか」を、画面から逆にたどってみます。
-
画面:本番のページにcommit:1a2b3c4dと表示されている
-
GitHub:リポジトリのトップ画面で、ファイル一覧のすぐ上にある最新コミットの行の右端、「🕘 ○ Commits」を開く。コミットの一覧が新しい順に並び、各行の右に 7 文字の短縮コミット ID が表示されているので、1a2b3c4dのものを探すと、誰が、いつ、どの変更を加えたかが分かります。


- ECR:ecs-handson-nginxで、タグが1a2b3c4…のイメージを探します。そのイメージダイジェスト(sha256:…)を確認します。
- ECS:サービスが使っているタスク定義のリビジョンを開き、イメージURIの@sha256:を確認します。3番のECRで確認したダイジェストと一致していれば、そのコミットからビルドされたイメージが稼働していることが確定できます。
この一連の確認で、障害が発生した際に「本番で何が動いているのか」をすぐに答えられることができます。
7.コラム:タグ戦略
この記事では、latestをパイプラインの起動の合図に使い、コミットIDのタグを追跡用に使っています。タグの付け方にはいくつか考え方があります。
| タグ | 利点 | 欠点 |
| latestだけ | 単純なためイメージ管理が1つ | 何が動いているか分からない。ロールバックもできない |
| v1,v2…と人がつける | 人間に分かりやすい | 番号の管理が手作業になり、付け忘れ・重複が起きる可能性がある |
| コミットID | 自動で一意。ソースに直結する。 | 人間には分かりにくい。 |
| コミットID+latest | 追跡もできて、パイプラインの起動も簡単になる。 | latestを上書きするためリポジトリを「ミュータブル」にする必要がある |
本番では、ECR のタグをイミュータブル(変更不可)にすることが推奨されます。一度付けたタグが別のイメージに付け替えられないので、「同じタグなのに中身が違う」事故を防げます。
その場合、latest を使った起動はできなくなるので、次のような構成に変更します。
-
CodePipelineのソースをS3やGitHubにし、GitHub Actionsがimagedefinitions.jsonを作る
- GitHub Actionsからパイプラインを直接起動する(aws codepipeline start-pipeline-execution)
- デプロイまでGitHub Actionsで行う
8.コラム:デプロイまでGitHub Actionsで実行する構成との比較
GitHub Actions には、ECS へのデプロイまで行う公式アクション(aws-actions/amazon-ecs-deploy-task-definition)もあります。「CodePipelineを使わず、GitHub Actionsで全部完結する」構成も広く使われています。
| 観点 | 今回の構成(CI: GitHub / CD: AWS) | GitHub Actions で全部実行 |
| GitHub に渡すAWS権限 | ECRへのpush だけ | ECS の更新、iam:PassRoleなども必要 |
| デプロイの承認 | ECSの一時停止フック(AWS 側) | GitHubのEnvironments の承認機能など |
| デプロイの履歴 | CodePipelineとECSに残る | GitHub Actionsの実行履歴に残る |
今回の構成を選んだ理由は、GitHubに渡す権限を最小にできることです。GitHubのリポジトリが乗っ取られた場合でも、攻撃者ができるのはイメージのpushまでで、本番に出すにはAWS側の承認を通る必要があります。
一方、小規模なチームで「GitHubだけ見ていればよい」ことを重視するなら、全部GitHub Actionsに寄せる構成にも十分な合理性があります。どちらが正解ということではなく、どこに責任の境界線を引くかの設計判断です。
まとめ
-
GitHub Actions からAWSへはOIDCで認証する … GitHub側に秘密情報を置かない
-
信頼ポリシーのsub条件がOIDCのセキュリティの要 … 絞らないと誰でも使える
-
2026年7月15日以降のリポジトリはsubにIDが付く … 古い記事の書き方では失敗する
-
GitHub Actionsに渡す権限はECRへのpushのみ … 本番に出す権限は AWS 側に残す
-
ECRを境界にすると、CIを差し替えてもCDはそのまま動く… 今回、パイプラインは一切変えていない
-
コミットID → ECRのタグ → ダイジェスト → タスク定義 … 本番で動いているコードを追跡できる
最後に
全てリソースを削除します
最初にGitHub側を削除します
-
リポジトリ … ecs-handson-app(Settings → Danger Zone → Delete this repository)削除
-
Personal access token … 1-2で発行した場合は削除します。
AWS側のリソースを以下の順番で削除していきます。
- CodePipeline:作成したパイプラインecs-handson-pipeline
- ECS → クラスター → サービス : ECS サービス ecs-handson-service
- ECS : ECS クラスター ecs-handson-cluster
- ECS : タスク定義 ecs-handson-task
- EC2 → ロードバランサー: ALB ecs-handson-alb (リスナーも一緒に消える)
- EC2 → ターゲットグループ: ターゲットグループ ecs-handson-tg-blue と ecs-handson-tg-green
- ECR : ECR リポジトリ ecs-handson-nginx
- S3 : S3 バケット 「codepipeline-ap-northeast-1-…」(必ずバケットの中を確認し、他の人や別プロジェクトで同じバケットを使用している場合はバケットは削除しないでください。)
- CloudWatch → ロググループ : CloudWatch ロググループ /ecs/ecs-handson-task と /aws/codepipeline/ecs-handson-pipeline
- IAMロール ecs-handson-github-actions
- IAMロール AWSCodePipelineServiceRole-ap-northeast-1-ecs-handson-pipeline
- IAMロール ecsInfrastructureRoleForLoadBalancers
- IAMポリシー ecs-handson-github-ecr-push
- IAM ID プロバイダー token.actions.githubusercontent.com (他の方や、他のプロジェクト等で使用している場合は、絶対に削除しないでください。)
- IAMロール ecsTaskExecutionRole (他の方や、他のプロジェクト等で使用している場合は、絶対に削除しないでください。)
- VPC : ecs-handson-vpc (VPC が削除できない場合は、ENI が残っていることがほとんどです。ALB やタスクを削除した直後は数分待ってから再試行してください。)
以上で完了です。お疲れ様でした。
次回は、これまでの内容を踏まえて応用編としてプライベートサブネットに配置した構成を試します。第5回は全てゼロから作りますので、ここまでのリソースは削除してください。(第5回へ移動する)