GitHub Actions と Terraform で構築する ECR + 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
前提条件と環境
Section titled “前提条件と環境”項目 | バージョン |
---|---|
Mac | Ventura 15.6 |
Terraform | 1.12.1 |
Terraform AWS Provider | 6.7.0 |
Go | 1.24.5 |
AWS CLI の設定確認
Section titled “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 リソース構築
Section titled “Terraform による AWS リソース構築”github_actions_repo = "<repo-org>/<repo-name>"lambda_image_tag = ""
以下のコマンドで Terraform を実行します。
cd terraformterraform initterraform planterraform apply
ECR リポジトリの作成
Section titled “ECR リポジトリの作成”プライベートリポジトリの設定
Section titled “プライベートリポジトリの設定”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
ライフサイクルポリシーの設定
Section titled “ライフサイクルポリシーの設定”古いイメージを自動削除するためのポリシーを設定します。最新の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 設定
Section titled “GitHub Actions 用の IAM 設定”AssumeRole 用 IAM ロール作成
Section titled “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
必要な権限ポリシーの設定
Section titled “必要な権限ポリシーの設定”GitHub Actions のワークフローからコンテナイメージを ECR へ push する場合、ecr:GetAuthorizationToken
の権限が必要です。
Amazon ECR では、ユーザーがレジストリへの認証を行って、Amazon ECR リポジトリに対するイメージのプッシュまたはプルを行う前に、IAM ポリシーを介して
ecr:GetAuthorizationToken
API への呼び出しを行う許可が必要です
引用:Amazon ECR でのプライベートリポジトリポリシー - AWS Documentation
OIDC Provider の設定
Section titled “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 設定
Section titled “Lambda 関数用 IAM 設定”Lambda 実行ロールの作成
Section titled “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 によるコンテナデプロイ
Section titled “GitHub Actions によるコンテナデプロイ”ワークフロー設定
Section titled “ワークフロー設定”ワークフロー全体を以下に示します。
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_TAG
OIDC を使った AWS 認証
Section titled “OIDC を使った 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-session
ECR ログインと Docker イメージのビルド
Section titled “ECR ログインと 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
ワークフローの実行
Section titled “ワークフローの実行”デプロイまでの設定が完了したので、実際にワークフローを実行してみます。
workflow_dispatch
を設定しているので、手動でワークフローを開始します。
on: workflow_dispatch:
実行結果を以下に示します。
権限確認とトラブルシューティング
Section titled “権限確認とトラブルシューティング”AssumeRole の動作確認
Section titled “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-session
CloudTrail でのイベント確認
Section titled “CloudTrail でのイベント確認”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 関数の作成
Section titled “Lambda 関数の作成”Terraform での Lambda 関数定義
Section titled “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 関数の実行とログ
Section titled “Lambda 関数の実行とログ”CLI での実行
Section titled “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.jsonnull
Management Console からの実行
Section titled “Management Console からの実行”Lambda のページからテストボタンで実行できます。
HTTPS URL からのアクセス
Section titled “HTTPS URL からのアクセス”2022年4月に Lambda Function URLs 機能が公開されました。この記事では Terraform でこの機能を設定しています。
resource "aws_lambda_function_url" "container_lambda" { # 設定内容}
AWS マネジメントコンソールからも URL を確認できます。
今回は URL を public に公開してるため、web ブラウザからアクセスできます。
ログの確認方法
Section titled “ログの確認方法”Lambda 関数の実行ログは CloudWatch Logs で確認できます。
# CloudWatch Log Group for Lambdaresource "aws_cloudwatch_log_group" "lambda_logs" { name = "/aws/lambda/container-lambda-function" retention_in_days = 14}
ローカル開発環境の整備
Section titled “ローカル開発環境の整備”チーム開発において、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!"}'null
curl を実行した結果を以下に示します。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 "("
Section titled “/app: line 1: syntax error: unexpected "("”distroless イメージの種類による動作の違い
Section titled “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/sh
debug タグでの問題
Section titled “debug タグでの問題”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 でのコンテナイメージ実行
この構成により、継続的デプロイメントの基盤が整いました。