GitHub Actions で Docker ビルド + ECR push を自動化する
概要
アプリケーションを ECS や Lambda(コンテナイメージ)にデプロイする際、コンテナイメージのビルドと Amazon ECR への push を手作業でやっていると、ローカル環境差によるビルドのぶれ、push し忘れ、タグの取り違え、そして「今動いているのはどのコミット由来か分からない」という追跡性の問題が起きがちです。
この記事では、GitHub Actions で「main ブランチへの push をトリガーに Docker イメージをビルドし、Amazon ECR へ push する」までを、OIDC による短命クレデンシャル方式で構築します。長期の IAM アクセスキーを GitHub Secrets に置かない、現在の推奨構成です。
環境
- GitHub Actions(
ubuntu-latestランナー) - Amazon ECR(プライベートリポジトリ)
- AWS 認証は OIDC(IAM ロールを AssumeRole)
- イメージビルドは
docker/build-push-action(Buildx) - リージョンは例として
ap-northeast-1
アカウントID・リポジトリ名は例示値です。実運用では自環境の値に読み替えてください。
なぜ OIDC 方式なのか
従来の「IAM アクセスキーを Secrets に登録する」方式は動作しますが、長期の秘密を外部サービスに預け続けることになり、漏洩時のダメージとローテーション負荷が大きいという弱点があります。
OIDC 方式では、ジョブ実行のたびに GitHub が発行する OIDC トークンと引き換えに、AWS STS が短命の一時クレデンシャルを払い出します。GitHub 側に長期の秘密を持たなくて済むのが最大の利点です。
ステップ1: OIDC プロバイダを登録する
アカウントに未登録の場合のみ実行します(1アカウントに1つで共用可能)。
aws iam create-open-id-connect-provider \ --url https://token.actions.githubusercontent.com \ --client-id-list sts.amazonaws.com \ --thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1
thumbprint は変わることがあります。Terraform 等の IaC では thumbprint を省略できるケースもあるため、最新のドキュメントを確認してください。
ステップ2: AssumeRole 用の IAM ロールを作る
信頼ポリシーで「どのリポジトリの、どの ref からのトークンを許可するか」を絞り込むのが肝心です。
trust-policy.json:
{
"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"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:my-org/my-app:ref:refs/heads/main"
}
}
}
]
}
注意: sub の条件を repo:my-org/my-app:* のようにワイルドカードで広げると、そのリポジトリのどのブランチ・PR・タグからでも AssumeRole できてしまいます。push トリガーで main だけを許可したいなら ref:refs/heads/main まで絞り込みましょう。
ロール作成とポリシーのアタッチ:
aws iam create-role \ --role-name github-actions-ecr-push \ --assume-role-policy-document file://trust-policy.json aws iam put-role-policy \ --role-name github-actions-ecr-push \ --policy-name ecr-push \ --policy-document file://ecr-policy.json
ecr-policy.json(最小権限):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "ecr:GetAuthorizationToken",
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"ecr:BatchCheckLayerAvailability",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:PutImage"
],
"Resource": "arn:aws:ecr:ap-northeast-1:123456789012:repository/my-app"
}
]
}
ecr:GetAuthorizationToken はリソース単位で絞れないため Resource: "*" になりますが、push 系アクションはリポジトリ ARN で絞れます。
ステップ3: ECR リポジトリを用意する
aws ecr create-repository \ --repository-name my-app \ --region ap-northeast-1 \ --image-scanning-configuration scanOnPush=true
scanOnPush=true にすると push のたびに脆弱性スキャンが走ります。
ステップ4: ワークフローを書く
.github/workflows/build-and-push.yml:
name: Build and Push to ECR
on:
push:
branches: [main]
permissions:
id-token: write # OIDC トークン発行に必須
contents: read
env:
AWS_REGION: ap-northeast-1
ECR_REPOSITORY: my-app
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-ecr-push
aws-region: ${{ env.AWS_REGION }}
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ github.sha }}
${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max
ポイント
permissions: id-token: write は必須 これが無いと OIDC トークンが発行されず、AssumeRole が失敗します。ハマりどころの筆頭です。ジョブレベルまたはワークフローレベルで明示してください。
タグにコミットSHAを付ける :latest だけだと稼働イメージがどのコミット由来か追えません。${{ github.sha }} を主タグにし、:latest を補助にすると、ロールバックもタグ指定で確実に行えます。
Buildx の GitHub Actions キャッシュ cache-from: type=gha / cache-to: type=gha,mode=max でレイヤーキャッシュを保存し、変更のないレイヤーの再ビルドをスキップできます。
確認方法
mainに push し、Actions タブでワークフローが緑になることを確認- ECR にイメージが増えているか確認
aws ecr list-images \ --repository-name my-app \ --region ap-northeast-1
configure-aws-credentialsのログで、意図した ARN の一時クレデンシャルを取得できているか確認(キー自体はマスクされます)
注意点
- 信頼ポリシーの
subを広げすぎない。ブランチ・タグ単位で絞る。 GetAuthorizationTokenのResource: "*"は仕様上避けられない。push 系だけ ARN で絞る。- キャッシュを効かせても Dockerfile のレイヤー順が悪いと効かない。変わりにくいレイヤー(依存インストール)を上、変わりやすいレイヤー(アプリコード COPY)を下に置く。
- マルチアーキ(arm64 等)が必要なら
platforms:を指定するが、QEMU エミュレーションで遅くなる。ネイティブが必要ならセルフホストランナーやマトリクスを検討する。 latest固定運用は上書きで前イメージを辿れなくなる。SHA タグ併用を推奨。
まとめ
- OIDC + AssumeRole で長期キーを GitHub に置かずに ECR へ push できる
- 信頼ポリシーの
sub条件の絞り込みがセキュリティの要 docker/build-push-action+ GitHub Actions キャッシュでビルドを短縮できる- SHA タグ運用でデプロイとロールバックの追跡性が上がる
この土台があれば、後段に ECS の update-service や Lambda の update-function-code を足すだけで、push からデプロイまで一気通貫にできます。
