Skip to content

GitHub Actions と Terraform で構築する ECR + Lambda コンテナデプロイ環境

GitHub Actions から ECR へのコンテナデプロイと Lambda でのコンテナ実行環境を構築した際の学習記録です。

学習内容

  • Terraform による AWS リソースの構築
  • OIDC による GitHub Actions の認証設定
  • ECR への自動デプロイの実装
  • Lambda のコンテナイメージ作成

blueprint

https://github.com/kntks/blog-code/tree/main/2025/08/github-actions-ecr-lambda-container-deploy

項目バージョン
MacVentura 15.6
Terraform1.12.1
Terraform AWS Provider6.7.0
Go1.24.5

以下のコマンドで AWS CLI が使用できることを確認します。

Terminal window
$ 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 リソース構築”
terraform/terraform.tfvars
github_actions_repo = "<repo-org>/<repo-name>"
lambda_image_tag = ""

以下のコマンドで Terraform を実行します。

Terminal window
cd terraform
terraform init
terraform plan
terraform apply

プライベートリポジトリの設定

Section titled “プライベートリポジトリの設定”

GitHub Actions でコンテナイメージを push する際、タグ名を commit hash にしています。コードへの commit の度にタグ名が変わるため、今回はイメージタグを MUTABLE に設定しています。

terraform/ecr.tf
# ECR Repository for Lambda container images
resource "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つのイメージを保持する設定にしています。

terraform/ecr.tf
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"
}
}
]
})
}
terraform/iam.tf
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
}

参考:

GitHub Actions のワークフローからコンテナイメージを ECR へ push する場合、ecr:GetAuthorizationToken の権限が必要です。

Amazon ECR では、ユーザーがレジストリへの認証を行って、Amazon ECR リポジトリに対するイメージのプッシュまたはプルを行う前に、IAM ポリシーを介して ecr:GetAuthorizationToken API への呼び出しを行う許可が必要です

引用:Amazon ECR でのプライベートリポジトリポリシー - AWS Documentation

AWS 公式の actions である configure-aws-credentials を使用します。AWS に ID プロバイダを作成するには以下の設定で十分です。

terraform/iam.tf
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

terraform/lambda.tf
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 function
resource "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 policy
resource "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 image
resource "aws_lambda_function" "container_lambda" {
count = var.create_lambda_function ? 1 : 0
# ...省略
}

参考:Resource: aws_lambda_function - Terraform

GitHub Actions によるコンテナデプロイ

Section titled “GitHub Actions によるコンテナデプロイ”

ワークフロー全体を以下に示します。

.github/workflows/github-actions-ecr-lambda-container-deploy.yml
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

GitHub Actions から AWS リソースへアクセスするために OIDC 認証を設定します。

AWS Role Arn を環境変数 AWS_ASSUME_ROLE_ECR に設定します。 github-actions-variables

configure-aws-credentials には、Role Arn、AWS Region、Role Session Name を設定するだけで十分です。

.github/workflows/github-actions-ecr-lambda-container-deploy.yml
- 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 の内容をほぼそのまま使用します。

.github/workflows/github-actions-ecr-lambda-container-deploy.yml
- 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

参考:https://github.com/aws-actions/amazon-ecr-login?tab=readme-ov-file#login-to-amazon-ecr-private-then-build-and-push-a-docker-image

コンテナイメージ作成に使用する Dockerfile を以下に示します。

Dockerfile
FROM golang:1.24.6-alpine3.22 AS build
WORKDIR /go/src/app
COPY 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 を使用します。
今回は受け取った引数を表示するだけの簡単なハンドラーを作成します。

src/main.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

ローカル環境では、以下のコマンドを実行します。

Terminal window
docker build . -t example

デプロイまでの設定が完了したので、実際にワークフローを実行してみます。

workflow_dispatch を設定しているので、手動でワークフローを開始します。

.github/workflows/github-actions-ecr-lambda-container-deploy.yml
on:
workflow_dispatch:

github-actions-workflow-dispatch

実行結果を以下に示します。 github-actions-workflow-dispatch-result

参考:ワークフローの手動実行 - GitHub Docs

権限確認とトラブルシューティング

Section titled “権限確認とトラブルシューティング”

ECR へのコンテナイメージプッシュの前に、以下のワークフローで AssumeRole が正常に動作するかを確認できます。

.github/workflows/assume-role-check.yml
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

参考:

GitHub Actions の step で実行した aws sts get-caller-identity 結果を以下に示します。

Terminal window
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.com
Assuming role with OIDC
Authenticated as assumedRoleId AAAAAAAAAAAA:github-actions-session

CloudTrail から実際に実行されたイベントを確認できます。

GitHub Actions で実行したときのイベントは、以下の項目で確認できます。

  • イベント名: AssumeRoleWithWebIdentity
  • イベントソース: sts.amazonaws.com
  • ユーザー名: repo:<repo-org>/<repo-name>:ref:refs/heads/main

イベント名を AssumeRoleWithWebIdentity で検索した結果を以下に示します。 cloud-trail-event-name

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

参考:Use IAM roles to connect GitHub Actions to actions in AWS - AWS blog

terraform/lambda.tf
# Lambda function using container image
resource "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 関数をデプロイします。

Terminal window
terraform plan -var="create_lambda_function=true"
terraform apply -var="create_lambda_function=true"

aws lambda invoke コマンドを使用して Lambda 関数を実行します。今回の Go のコードは error のみを return するため、response.json には null のみが表示されます。

Terminal window
$ aws lambda invoke --function-name container-lambda-function response.json
{
"StatusCode": 200,
"ExecutedVersion": "$LATEST"
}
$ cat response.json
null

Lambda のページからテストボタンで実行できます。 management-console-lambda-test

2022年4月に Lambda Function URLs 機能が公開されました。この記事では Terraform でこの機能を設定しています。

terraform/lambda.tf
resource "aws_lambda_function_url" "container_lambda" {
# 設定内容
}

AWS マネジメントコンソールからも URL を確認できます。
今回は URL を public に公開してるため、web ブラウザからアクセスできます。 lambda-function-urls

Lambda 関数の実行ログは CloudWatch Logs で確認できます。

terraform/lambda.tf
# CloudWatch Log Group for Lambda
resource "aws_cloudwatch_log_group" "lambda_logs" {
name = "/aws/lambda/container-lambda-function"
retention_in_days = 14
}

lambda-cloudwatch-log-group

チーム開発において、Lambda コードは複数のメンバーでメンテナンスします。ローカル開発環境を整備し、保守しやすい環境を用意しておくことは大切です。

AWS の OS 専用ベースイメージを使用する場合、ランタイムインターフェイスエミュレーターが含まれています。

Dockefile.local
FROM golang:1.24.6-alpine3.22 AS build
WORKDIR /go/src/app
COPY 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"]

イメージを作成します。

Terminal window
$ docker build . -t lambda-local -f Dockerfile.local

コンテナを起動します。

Terminal window
$ docker run -p 9000:8080 --entrypoint /usr/local/bin/aws-lambda-rie --rm lambda-local

以下のコマンドを別ターミナルで実行します。

Terminal window
$ curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"payload":"hello world!"}'
null

curl を実行した結果を以下に示します。POST データの payload:hello world! が表示されていることが確認できます。

Terminal window
START RequestId: f419458c-ade2-4b22-b1b5-637d33147f6e Version: $LATEST
12 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?: false
12 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-602000e5957f
REPORT 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

参考:

/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 が含まれています。

Terminal window
$ docker run --entrypoint=sh --rm -ti gcr.io/distroless/static-debian12:debug
/ # which sh
/busybox/sh

debug タグを使用した Dockerfile でコンテナイメージを作成して Lambda で実行すると、以下のエラーが発生します。

Dockerfile
FROM golang:1.24.6-alpine3.22 AS build
WORKDIR /go/src/app
COPY 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.ExitError
START RequestId: 763ddbf2-6b42-47d2-abd4-fc6a112e71a3 Version: $LATEST
RequestId: 763ddbf2-6b42-47d2-abd4-fc6a112e71a3 Error: Runtime exited with error: exit status 2
Runtime.ExitError
END RequestId: 763ddbf2-6b42-47d2-abd4-fc6a112e71a3
REPORT RequestId: 763ddbf2-6b42-47d2-abd4-fc6a112e71a3 Duration: 59.52 ms Billed Duration: 60 ms Memory Size: 128 MB Max Memory Used: 3 MB

debug イメージを使用したい場合は、CMDENTRYPOINT に変更すると正常に実行できます。

Dockerfile
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 でのコンテナイメージ実行

この構成により、継続的デプロイメントの基盤が整いました。