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 applyECR リポジトリの作成
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:GetAuthorizationTokenAPI への呼び出しを行う許可が必要です
引用: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_TAGOIDC を使った 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-sessionECR ログインと 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-sessionCloudTrail でのイベント確認
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.jsonnullManagement 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!"}'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 "("
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/shdebug タグでの問題
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 MBdebug イメージを使用したい場合は、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 でのコンテナイメージ実行
 
この構成により、継続的デプロイメントの基盤が整いました。