GitHub Actions と Terraform で構築する ECR + Lambda コンテナデプロイ環境
GitHub Actions から ECR への Docker コンテナ自動デプロイと Lambda でのコンテナ実行環境を構築した際の学習記録です。Terraform による AWS リソース構築、OIDC を利用した GitHub Actions の認証設定、ECR への継続的デプロイの実装手順をまとめました。distroless イメージでの Lambda 実行時の注意点も記録しています。
概要
GitHub Actions から ECR へのコンテナデプロイと Lambda でのコンテナ実行環境を構築した際の学習記録です。
学習内容
- Terraform による AWS リソースの構築
- OIDC による GitHub Actions の認証設定
- ECR への自動デプロイの実装
- Lambda のコンテナイメージ作成
成果物
https://github.com/kntks/blog-code/tree/main/2025/08/github-actions-ecr-lambda-container-deploy
前提条件と環境
バージョン
| 項目 | バージョン |
|---|---|
| Mac | Ventura 15.6 |
| Terraform | 1.12.1 |
| Terraform AWS Provider | 6.7.0 |
| Go | 1.24.5 |
AWS CLI の設定確認
以下のコマンドで AWS CLI が使用できることを確認します。
$ aws sts get-caller-identity --profile sandbox-1{ "UserId": "XXXXXXXXXXXXXX:<username>", "Account": "<account-id>", "Arn": "arn:aws:sts::<account-id>:assumed-role/AWSReservedSSO_AdministratorAccess_1111111111111/<username>"}Terraform による AWS リソース構築
github_actions_repo = "<repo-org>/<repo-name>"lambda_image_tag = ""以下のコマンドで Terraform を実行します。
cd terraformterraform initterraform planterraform applyECR リポジトリの作成
プライベートリポジトリの設定
GitHub Actions でコンテナイメージを push する際、タグ名を commit hash にしています。コードへの commit の度にタグ名が変わるため、今回はイメージタグを MUTABLE に設定しています。
# ECR Repository for Lambda container imagesresource "aws_ecr_repository" "lambda_repo" { name = "lambda-container-app" image_tag_mutability = "MUTABLE"
image_scanning_configuration { scan_on_push = true }
encryption_configuration { encryption_type = "AES256" }}参考:Resource: aws_ecr_repository - Terraform
ライフサイクルポリシーの設定
古いイメージを自動削除するためのポリシーを設定します。最新の2つのイメージを保持する設定にしています。
resource "aws_ecr_lifecycle_policy" "lambda_repo_policy" { repository = aws_ecr_repository.lambda_repo.name
policy = jsonencode({ rules = [ { rulePriority = 1 description = "Keep last 2 images" selection = { tagStatus = "any" countType = "imageCountMoreThan" countNumber = 2 } action = { type = "expire" } } ] })}GitHub Actions 用の IAM 設定
AssumeRole 用 IAM ロール作成
data "aws_iam_policy_document" "github_actions_assume_role" { statement { effect = "Allow"
actions = [ "sts:AssumeRoleWithWebIdentity" ]
principals { type = "Federated" identifiers = [aws_iam_openid_connect_provider.github_actions.arn] }
condition { test = "StringLike" variable = "token.actions.githubusercontent.com:sub" values = ["repo:${var.github_actions_repo}:*"] }
condition { test = "StringEquals" variable = "token.actions.githubusercontent.com:aud" values = ["sts.amazonaws.com"] } }}
# GitHub Actions用のIAM roleとポリシーを作成resource "aws_iam_role" "github_actions" { name = "github-actions-ecr-lambda-deploy" assume_role_policy = data.aws_iam_policy_document.github_actions_assume_role.json}
data "aws_iam_policy_document" "push_ecr" { statement { effect = "Allow"
actions = [ "ecr:BatchCheckLayerAvailability", "ecr:GetDownloadUrlForLayer", "ecr:BatchGetImage", "ecr:PutImage", "ecr:InitiateLayerUpload", "ecr:UploadLayerPart", "ecr:CompleteLayerUpload", ]
resources = [ aws_ecr_repository.lambda_repo.arn ] }
// https://github.com/aws-actions/amazon-ecr-login?tab=readme-ov-file#ecr-private statement { effect = "Allow"
actions = [ "ecr:GetAuthorizationToken" ]
resources = [ "*" ] }}
# GitHub ActionsでECRにアクセスするためのポリシーresource "aws_iam_role_policy" "github_actions_policy" { name = "github-actions-ecr-policy" role = aws_iam_role.github_actions.id policy = data.aws_iam_policy_document.push_ecr.json}参考:
- Configuring IAM to trust GitHub - GitHub
- アマゾン ウェブ サービスでの OpenID Connect の構成 - Github Docs
- OpenID Connect リファレンス - GitHub Docs
必要な権限ポリシーの設定
GitHub Actions のワークフローからコンテナイメージを ECR へ push する場合、ecr:GetAuthorizationToken の権限が必要です。
Amazon ECR では、ユーザーがレジストリへの認証を行って、Amazon ECR リポジトリに対するイメージのプッシュまたはプルを行う前に、IAM ポリシーを介して
ecr:GetAuthorizationTokenAPI への呼び出しを行う許可が必要です
引用:Amazon ECR でのプライベートリポジトリポリシー - AWS Documentation
OIDC Provider の設定
AWS 公式の actions である configure-aws-credentials を使用します。AWS に ID プロバイダを作成するには以下の設定で十分です。
resource "aws_iam_openid_connect_provider" "github_actions" { url = "https://token.actions.githubusercontent.com"
client_id_list = [ "sts.amazonaws.com", ]}参考:Resource: aws_iam_openid_connect_provider - Terraform
Lambda 関数用 IAM 設定
Lambda 実行ロールの作成
data "aws_iam_policy_document" "lambda_assume_role" { statement { effect = "Allow"
principals { type = "Service" identifiers = ["lambda.amazonaws.com"] }
actions = [ "sts:AssumeRole" ] }}
# IAM role for Lambda functionresource "aws_iam_role" "lambda_execution_role" { name = "lambda-container-execution-role" assume_role_policy = data.aws_iam_policy_document.lambda_assume_role.json}
# Attach basic Lambda execution policyresource "aws_iam_role_policy_attachment" "lambda_basic_execution" { policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" role = aws_iam_role.lambda_execution_role.name}
# Lambda function using container imageresource "aws_lambda_function" "container_lambda" { count = var.create_lambda_function ? 1 : 0 # ...省略}参考:Resource: aws_lambda_function - Terraform
GitHub Actions によるコンテナデプロイ
ワークフロー設定
ワークフロー全体を以下に示します。
name: Deploy ECR from GitHub Action
on: workflow_dispatch:
permissions: id-token: write contents: read
jobs: deploy-container: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4
- name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ vars.AWS_ASSUME_ROLE_ECR }} aws-region: ap-northeast-1 role-session-name: github-actions-session
- name: Log in to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v2
- name: Build, tag, and push docker image to Amazon ECR env: REGISTRY: ${{ steps.login-ecr.outputs.registry }} REPOSITORY: lambda-container-app IMAGE_TAG: ${{ github.sha }} run: | cd github-actions-ecr-lambda-container-deploy docker build -t $REGISTRY/$REPOSITORY:$IMAGE_TAG . docker push $REGISTRY/$REPOSITORY:$IMAGE_TAGOIDC を使った AWS 認証
GitHub Actions から AWS リソースへアクセスするために OIDC 認証を設定します。
AWS Role Arn を環境変数 AWS_ASSUME_ROLE_ECR に設定します。

configure-aws-credentials には、Role Arn、AWS Region、Role Session Name を設定するだけで十分です。
- name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ vars.AWS_ASSUME_ROLE_ECR }} aws-region: ap-northeast-1 role-session-name: github-actions-sessionECR ログインと Docker イメージのビルド
コンテナイメージを push する前に ECR にログインします。
GitHub Actions のワークフローからコンテナイメージを ECR へ push するために aws-actions/amazon-ecr-login を使用します。
設定は、README.md の内容をほぼそのまま使用します。
- name: Log in to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v2
- name: Build, tag, and push docker image to Amazon ECR env: REGISTRY: ${{ steps.login-ecr.outputs.registry }} REPOSITORY: lambda-container-app IMAGE_TAG: ${{ github.sha }} run: | cd github-actions-ecr-lambda-container-deploy docker build -t $REGISTRY/$REPOSITORY:$IMAGE_TAG . docker push $REGISTRY/$REPOSITORY:$IMAGE_TAGコンテナイメージ作成に使用する Dockerfile を以下に示します。
FROM golang:1.24.6-alpine3.22 AS build
WORKDIR /go/src/appCOPY src/ .
RUN go mod download
RUN CGO_ENABLED=0 go build -o /go/bin/app
FROM gcr.io/distroless/static-debian12
COPY --from=build /go/bin/app /CMD ["/app"]Lambda で実行するコードを Go 言語で書きます。このとき、aws/aws-lambda-go を使用します。
今回は受け取った引数を表示するだけの簡単なハンドラーを作成します。
package main
import ( "context" "fmt"
"github.com/aws/aws-lambda-go/lambda")
func main() { lambda.Start(Handler)}
func Handler(ctx context.Context, request any) error { fmt.Println("Request received:", request) return nil}参考:コンテナイメージを使用して Go Lambda 関数をデプロイする - AWS Documentation
ローカル環境では、以下のコマンドを実行します。
docker build . -t exampleワークフローの実行
デプロイまでの設定が完了したので、実際にワークフローを実行してみます。
workflow_dispatch を設定しているので、手動でワークフローを開始します。
on: workflow_dispatch:
実行結果を以下に示します。

権限確認とトラブルシューティング
AssumeRole の動作確認
ECR へのコンテナイメージプッシュの前に、以下のワークフローで AssumeRole が正常に動作するかを確認できます。
name: Assume Role and Check Identity
on: workflow_dispatch:
permissions: id-token: write contents: read
jobs: assume-role-and-check: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4
- name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::<account-id>:role/github-actions-ecr-lambda-deploy aws-region: ap-northeast-1 role-session-name: github-actions-session
- name: Check identity run: | aws sts get-caller-identity参考:
- configure-aws-credentials - GitHub
- Use IAM roles to connect GitHub Actions to actions in AWS - AWS blog
実行結果
GitHub Actions の step で実行した aws sts get-caller-identity 結果を以下に示します。
Run aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::111111111111:role/github-actions-ecr-lambda-deploy aws-region: ap-northeast-1 role-session-name: github-actions-session audience: sts.amazonaws.comAssuming role with OIDCAuthenticated as assumedRoleId AAAAAAAAAAAA:github-actions-sessionCloudTrail でのイベント確認
CloudTrail から実際に実行されたイベントを確認できます。
GitHub Actions で実行したときのイベントは、以下の項目で確認できます。
- イベント名:
AssumeRoleWithWebIdentity - イベントソース:
sts.amazonaws.com - ユーザー名:
repo:<repo-org>/<repo-name>:ref:refs/heads/main
イベント名を AssumeRoleWithWebIdentity で検索した結果を以下に示します。

イベントソースを sts.amazonaws.com で検索した結果を以下に示します。

参考:Use IAM roles to connect GitHub Actions to actions in AWS - AWS blog
Lambda 関数の作成
Terraform での Lambda 関数定義
# Lambda function using container imageresource "aws_lambda_function" "container_lambda" { count = var.create_lambda_function ? 1 : 0
function_name = "container-lambda-function" role = aws_iam_role.lambda_execution_role.arn
# Use container image from ECR package_type = "Image" image_uri = "${aws_ecr_repository.lambda_repo.repository_url}:${var.lambda_image_tag}"
# Container configuration timeout = 30 memory_size = 128
# Environment variables (optional) environment { variables = { ENV = "dev" } }
# Ensure the image exists before creating the function depends_on = [ aws_ecr_repository.lambda_repo, aws_cloudwatch_log_group.lambda_logs, ]}以下のコマンドで Terraform を実行し、Lambda 関数をデプロイします。
terraform plan -var="create_lambda_function=true"terraform apply -var="create_lambda_function=true"Lambda 関数の実行とログ
CLI での実行
aws lambda invoke コマンドを使用して Lambda 関数を実行します。今回の Go のコードは error のみを return するため、response.json には null のみが表示されます。
$ aws lambda invoke --function-name container-lambda-function response.json{ "StatusCode": 200, "ExecutedVersion": "$LATEST"}
$ cat response.jsonnullManagement Console からの実行
Lambda のページからテストボタンで実行できます。

HTTPS URL からのアクセス
2022年4月に Lambda Function URLs 機能が公開されました。この記事では Terraform でこの機能を設定しています。
resource "aws_lambda_function_url" "container_lambda" { # 設定内容}AWS マネジメントコンソールからも URL を確認できます。
今回は URL を public に公開してるため、web ブラウザからアクセスできます。

ログの確認方法
Lambda 関数の実行ログは CloudWatch Logs で確認できます。
# CloudWatch Log Group for Lambdaresource "aws_cloudwatch_log_group" "lambda_logs" { name = "/aws/lambda/container-lambda-function" retention_in_days = 14}
その他
ローカル開発環境の整備
チーム開発において、Lambda コードは複数のメンバーでメンテナンスします。ローカル開発環境を整備し、保守しやすい環境を用意しておくことは大切です。
AWS の OS 専用ベースイメージを使用する場合、ランタイムインターフェイスエミュレーターが含まれています。
FROM golang:1.24.6-alpine3.22 AS build
WORKDIR /go/src/appCOPY src/ .
RUN go mod download
RUN CGO_ENABLED=0 go build -o bootstrap .
FROM public.ecr.aws/lambda/provided:al2023
COPY --from=build /go/src/app/bootstrap ${LAMBDA_TASK_ROOT}CMD ["bootstrap"]イメージを作成します。
$ docker build . -t lambda-local -f Dockerfile.localコンテナを起動します。
$ docker run -p 9000:8080 --entrypoint /usr/local/bin/aws-lambda-rie --rm lambda-local以下のコマンドを別ターミナルで実行します。
$ curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"payload":"hello world!"}'nullcurl を実行した結果を以下に示します。POST データの payload:hello world! が表示されていることが確認できます。
START RequestId: f419458c-ade2-4b22-b1b5-637d33147f6e Version: $LATEST12 Aug 2025 10:14:35,379 [INFO] (rapid) INIT START(type: on-demand, phase: init)12 Aug 2025 10:14:35,380 [INFO] (rapid) The extension's directory "/opt/extensions" does not exist, assuming no extensions to be loaded.12 Aug 2025 10:14:35,380 [INFO] (rapid) Starting runtime without AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN , Expected?: false12 Aug 2025 10:14:35,503 [INFO] (rapid) INIT RTDONE(status: success)12 Aug 2025 10:14:35,503 [INFO] (rapid) INIT REPORT(durationMs: 123.709000)12 Aug 2025 10:14:35,503 [INFO] (rapid) INVOKE START(requestId: 54a9006a-32db-4974-82ed-602000e5957f)12 Aug 2025 10:14:35,510 [INFO] (rapid) INVOKE RTDONE(status: success, produced bytes: 0, duration: 6.628000ms)Request received: map[payload:hello world!]END RequestId: 54a9006a-32db-4974-82ed-602000e5957fREPORT RequestId: 54a9006a-32db-4974-82ed-602000e5957f Init Duration: 0.18 ms Duration: 130.71 ms Billed Duration: 131 ms Memory Size: 3008 MB Max Memory Used: 3008 MB参考:
- aws/aws-lambda-runtime-interface-emulator
- lambda/provided
- 第二回 コンテナ Lambda を開発、まずは RIC と RIE を使ってみよう !
/app: line 1: syntax error: unexpected "("
distroless イメージの種類による動作の違い
今回は gcr.io/distroless/static-debian12 のイメージを使用しています。
distroless のイメージタグには debug があり、busybox/sh が含まれています。
$ docker run --entrypoint=sh --rm -ti gcr.io/distroless/static-debian12:debug
/ # which sh/busybox/shdebug タグでの問題
debug タグを使用した Dockerfile でコンテナイメージを作成して Lambda で実行すると、以下のエラーが発生します。
FROM golang:1.24.6-alpine3.22 AS build
WORKDIR /go/src/appCOPY src/ .
RUN go mod download
RUN CGO_ENABLED=0 go build -o /go/bin/app
FROM gcr.io/distroless/static-debian12:debug
COPY --from=build /go/bin/app /CMD ["/app"]ビルドコマンドは docker build -t $REGISTRY/$REPOSITORY:$IMAGE_TAG . です。
Lambda でテスト実行すると以下のエラーが発生します。
/app: line 1: syntax error: unexpected "("INIT_REPORT Init Duration: 218.45 ms Phase: init Status: error Error Type: Runtime.ExitError/app: line 1: syntax error: unexpected "("INIT_REPORT Init Duration: 1.02 ms Phase: invoke Status: error Error Type: Runtime.ExitErrorSTART RequestId: 763ddbf2-6b42-47d2-abd4-fc6a112e71a3 Version: $LATESTRequestId: 763ddbf2-6b42-47d2-abd4-fc6a112e71a3 Error: Runtime exited with error: exit status 2Runtime.ExitErrorEND RequestId: 763ddbf2-6b42-47d2-abd4-fc6a112e71a3REPORT RequestId: 763ddbf2-6b42-47d2-abd4-fc6a112e71a3 Duration: 59.52 ms Billed Duration: 60 ms Memory Size: 128 MB Max Memory Used: 3 MB解決方法
debug イメージを使用したい場合は、CMD を ENTRYPOINT に変更すると正常に実行できます。
FROM gcr.io/distroless/static-debian12:debug
COPY --from=build /go/bin/app /ENTRYPOINT ["/app"]原因: CMD で指定されたコマンドがシェル経由で実行されるため、バイナリファイルがシェルスクリプトとして解釈されてエラーが発生した可能性があります。
参考:
まとめ
GitHub Actions から ECR へのコンテナデプロイと Lambda でのコンテナ実行環境を構築しました。主なポイントは以下の通りです。
- Terraform による AWS リソースの自動構築
- OIDC による GitHub Actions の安全な AWS 認証
- ECR への自動コンテナデプロイ
- Lambda でのコンテナイメージ実行
この構成により、継続的デプロイメントの基盤が整いました。