Go言語のLambda関数をコンテナイメージでデプロイする手順|provided.al2023対応
Go言語でAWS Lambdaを書くとき、コンテナイメージを選ぶ理由は非圧縮10GBの容量枠と、フォントやネイティブライブラリをOSごと同梱できる自由度にあります。一方で、コンテナにした関数だけに効く制約もあります。2026年7月31日にはprovided.al2が廃止され、Go用のAWSベースイメージは実質provided.al2023の一択になりました。この記事では、マルチステージビルドによるDockerfileの実装からローカル検証、Amazon ECRへの登録と関数の更新までを通しで示し、ZIPでは踏まない運用上の落とし穴を整理します。
まとめ
- Go用のAWSベースイメージは
public.ecr.aws/lambda/provided:al2023のみです。provided.al2は2026年7月31日に廃止済みで、ベースイメージへのパッチ提供も終了しています。 - コンテナイメージではバイナリ名を
bootstrapにする必要がありません。DockerfileのENTRYPOINTが起動対象を決めます。 - ビルドは
docker buildx buildで行い、--provenance=falseを必ず付けます。省略するとLambdaがイメージを受け付けません。--platformも実行環境に合わせて明示します。 - ECRの同じタグに再pushしても関数は切り替わりません。
update-function-codeの実行が別途必要です。 - コンテナイメージ関数にはランタイム廃止の事前通知が届かず、SnapStartも使えません。数週間呼ばれないとInactiveになり、初回呼び出しが弾かれます。
以下では、この判断と手順の根拠を順に見ていきます。
ZIPアーカイブではなくコンテナイメージを選ぶ判断基準
Goはネイティブバイナリにコンパイルされるため、言語ランタイムを同梱する必要がありません。単純なハンドラであればZIPで十分に動きます。それでもコンテナイメージを選ぶ理由は、容量とOSレイヤの制御に集約されます。
| 項目 | ZIPアーカイブ | コンテナイメージ |
|---|---|---|
| サイズ上限 | 50MB/250MB | 10GB |
| OSパッケージの同梱 | 不可 | 可 |
| 廃止の事前通知 | 180日前に3経路 | 提供されない |
| SnapStart | Java・Python・.NETのみ | 非対応 |
| 必要なビルド環境 | Goツールチェーン | Docker 25.0.0以降 |
サイズ上限はZIPが50MB(zip状態)と250MB(解凍後・レイヤー含む)、コンテナが非圧縮10GB(全レイヤ込み)です。廃止通知の3経路とはメール、Health Dashboard、Trusted Advisorを指します。SnapStartが使えるのはJava 11以降・Python 3.12以降・.NET 8以降のマネージドランタイムだけなので、GoはZIPで作った場合でも対象外です。コンテナのビルドにはbuildxプラグインも必要になります。
判断が分かれるのは中間帯です。依存が純Goで完結し、成果物が数十MBに収まるならZIPのほうが運用は軽くなります。PDF生成のために日本語フォントを積む、cgo経由で共有ライブラリを使う、機械学習モデルのような数百MB級のアセットを同梱する。こうした条件が1つでも入るとZIPの250MB枠が先に効いてくるため、コンテナイメージが現実解になります。デプロイパッケージの形式は既存の関数では変更できず、ZIPからの切り替えは新規関数の作成になる点も判断材料に入れてください。Lambda全体の課金構造や実行モデルの前提はAWS Lambdaの仕組みと料金体系の解説にまとめています。
ベースイメージの選択とprovided.al2廃止への対応
provided.al2の廃止日とコンテナ関数への影響
AWSはAmazon Linux 2のEOLを2026年6月30日とし、これに合わせてprovided.al2を2026年7月31日に廃止しました。ZIPでprovided.al2ランタイムを使っている関数は、2027年2月1日以降は新規作成が、2027年3月3日以降は更新がブロックされます。
コンテナイメージでは挙動が違います。ランタイム識別子ではなくベースイメージが実体なので、Lambda API側でブロックされません。代わりに、廃止日をもってAWSがベースイメージへセキュリティパッチを供給する責任を終了します。動き続ける一方で脆弱性が塞がれなくなる、という形の影響です。FROM public.ecr.aws/lambda/provided:al2のままのDockerfileが残っているなら、タグをal2023に差し替えて再ビルドしてください。
provided.al2023の中身とサイズ
provided.al2023はAmazon Linux 2023のminimalコンテナイメージをベースにしており、40MB未満です。約109MBあったprovided.al2から4割弱まで縮みました。廃止予定日は2029年6月30日で、当面は移行先を考える必要がありません。
イメージの中身は公開されているDockerfileで確認できます。FROM scratchにAL2023のtarballを展開し、WORKDIRを/var/task、ENTRYPOINTを["/lambda-entrypoint.sh"]、LANGをen_US.UTF-8に設定した構成です。WORKDIRが/var/taskである点は後述のDockerfileで効いてきます。
非AWSベースイメージを選ぶ条件
AlpineやDebianなど、AWS以外のベースイメージも使えます。ただしその場合はRuntime Interface Emulatorが同梱されず、ローカル検証のたびに自分でRIEバイナリを配置する手間が増えます。社内の共通ベースイメージにセキュリティ設定が集約されている、といった組織側の事情がなければprovided.al2023が第一候補です。
どのベースイメージを選んでも、Goハンドラにはaws-lambda-go/lambdaパッケージを含める必要があります。これがLambda Runtime APIを実装するランタイムインターフェースクライアントで、無いイメージはLambda上で起動しません。
マルチステージビルドによるDockerfileの実装
ハンドラ実装とaws-lambda-goの導入
プロジェクトを初期化し、aws-lambda-goを依存に追加します。2026年8月時点の最新はv1.54.0(2026年3月23日公開)です。
mkdir hello
cd hello
go mod init example.com/hello-world
go get github.com/aws/aws-lambda-go/lambda
ハンドラはlambda.Startに関数を渡すだけです。API Gatewayからの呼び出しを受ける最小構成は次のようになります。
package main
import (
"context"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
func handler(ctx context.Context, event events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
return events.APIGatewayProxyResponse{
StatusCode: 200,
Body: "Hello from Lambda!",
}, nil
}
func main() {
lambda.Start(handler)
}
第1引数のcontext.Contextからは残り実行時間やリクエストIDを取得できます。第2引数のイベント型は呼び出し元に対応させる必要があり、API Gateway(REST API)ならevents.APIGatewayProxyRequest、HTTP APIならevents.APIGatewayV2HTTPRequest、S3通知ならevents.S3Eventです。型が合っていないとJSONのアンマーシャルで失敗します。
Dockerfileの全文と各行の役割
ビルド用のGoイメージと、デプロイ用のprovided.al2023を分けたマルチステージ構成にします。ビルドステージのGoツールチェーンは最終イメージに残らないため、成果物は数MB〜十数MBに収まります。
FROM golang:1.26 AS build
WORKDIR /helloworld
COPY go.mod go.sum ./
RUN go mod download
COPY main.go .
RUN CGO_ENABLED=0 go build -tags lambda.norpc -o main main.go
FROM public.ecr.aws/lambda/provided:al2023
COPY --from=build /helloworld/main ./main
ENTRYPOINT [ "./main" ]
ビルドステージのGoバージョンは、手元で開発に使っているバージョンと揃えてください。AWS公式ドキュメントの例はgolang:1.20のまま更新されていませんが、2026年8月時点の最新安定版はgo1.26.5です。
-tags lambda.norpcは、lambdaパッケージからRPCコンポーネントを除外するビルドタグです。RPCは廃止済みのgo1.xランタイムでのみ必要だったもので、コンテナイメージでは使いません。付けるとバイナリが小さくなります。CGO_ENABLED=0は静的リンクを強制する指定で、ビルドステージのDebian系イメージとデプロイステージのAL2023でglibcのバージョンが違うために起きる起動失敗を防ぎます。cgo経由でネイティブライブラリを使う場合はこの指定を外し、ビルドステージもpublic.ecr.aws/amazonlinux/amazonlinux:2023に揃えてglibcを一致させてください。
COPY --from=build /helloworld/main ./mainの相対パスは、ベースイメージのWORKDIRである/var/taskを基準に解決されます。バイナリは/var/task/mainに置かれ、ENTRYPOINTの./mainがこれを指します。
意図的に書いていない行が1つあります。USER命令です。標準のDockerはUSER未指定ならrootで動きますが、Lambdaはコンテナイメージを起動する際に最小権限のLinuxユーザーを自動で定義します。USERを書く必要はなく、代わりに「そのユーザーが読めるファイル配置になっているか」を確認してください。他ユーザーからの実行が制限されたファイルに依存していると、ここで落ちます。
バイナリ名にbootstrapが不要な理由
ZIPアーカイブでprovided.al2023ランタイムを使う場合、実行ファイルはbootstrapという名前でなければなりません。Lambdaがその名前を決め打ちで起動するためです。この知識があると、コンテナイメージでもbootstrapにリネームすべきだと考えがちですが、必要ありません。
コンテナイメージではDockerfileのENTRYPOINTが起動対象を明示するため、バイナリ名は任意です。AWS公式ドキュメントのGo向け例もENTRYPOINT [ "./main" ]を採用しています。書き忘れには注意してください。ベースイメージ側のENTRYPOINTは["/lambda-entrypoint.sh"]のままなので、自分のバイナリが起動対象になりません。
docker buildxで省略できないオプション
ビルドにはdocker buildxを使います。前提としてDockerは25.0.0以降、buildxプラグインの導入が必要です。
docker buildx build --platform linux/amd64 --provenance=false -t docker-image:test .
--provenance=falseはAWSが必須と明記しているオプションです。理由そのものは公式に書かれていませんが、buildxが既定で付与するprovenance attestationによってイメージがimage index形式になり、単一アーキテクチャのイメージしか受け付けないLambda側の要件と衝突するためと理解されています。「ローカルでは動いたのにLambdaに登録できない」という詰まり方の典型がこれです。
--platformも明示してください。ビルドマシンのアーキテクチャに関係なく、Lambdaの実行環境に合わせる必要があります。Apple Silicon搭載のMacでx86_64向けの関数を作るならlinux/amd64、Graviton(arm64)で動かすならlinux/arm64です。イメージは片方のアーキテクチャだけを対象にします。
ここで注意したいのが、ビルドマシンと異なるアーキテクチャを指定するとビルドステージのGoイメージまでQEMUエミュレーションで動き、コンパイルが極端に遅くなる点です。Goはクロスコンパイルできるので、ビルドステージだけはビルドマシンのアーキテクチャで動かすほうが速く終わります。
FROM --platform=$BUILDPLATFORM golang:1.26 AS build
ARG TARGETARCH
RUN GOOS=linux GOARCH=$TARGETARCH CGO_ENABLED=0 go build -tags lambda.norpc -o main main.go
マニフェストサイズは25,400バイト未満が推奨で、レイヤー数とアノテーションを減らすと抑えられます。レイヤーキャッシュやsecretマウントを含むビルド側の最適化はBuildKitのキャッシュ設計とマルチアーキ対応を参照してください。
ランタイムインターフェースエミュレータでのローカル検証
ECRにpushする前に、手元でハンドラが起動するかを確認します。Runtime Interface Emulator(RIE)はprovided.al2023ベースイメージに同梱されており、/usr/local/bin/aws-lambda-rieに配置されています。追加のダウンロードは不要です。
docker run -d -p 9000:8080 \
--entrypoint /usr/local/bin/aws-lambda-rie \
docker-image:test ./main
--entrypointでRIEを起動し、その引数として本来のENTRYPOINTである./mainを渡す形です。この引数は省略できません。RIEは起動対象のコマンドを引数から受け取る設計だからです。arm64向けにビルドしたイメージなら--platform linux/arm64を併せて指定してください。
起動するとlocalhost:9000/2015-03-31/functions/function/invocationsにローカルエンドポイントが立ちます。別のターミナルからイベントをPOSTして応答を確認します。
curl "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"payload":"hello world!"}'
このエンドポイントのパスは固定で、関数名の部分は常にfunctionです。実際の関数名に置き換える必要はありません。検証が終わったらdocker psでコンテナIDを確認し、docker killで停止します。
Amazon ECRへの登録とLambda関数の作成・更新
ECRリポジトリ作成とイメージのpush
Lambdaが参照できるのはAmazon ECRのプライベートリポジトリだけです。リポジトリは関数と同じリージョンに作る必要があります。別リージョンのイメージは指定できません。
aws ecr get-login-password --region ap-northeast-1 \
| docker login --username AWS --password-stdin 111122223333.dkr.ecr.ap-northeast-1.amazonaws.com
aws ecr create-repository --repository-name hello-world --region ap-northeast-1 \
--image-scanning-configuration scanOnPush=true --image-tag-mutability MUTABLE
docker tag docker-image:test 111122223333.dkr.ecr.ap-northeast-1.amazonaws.com/hello-world:latest
docker push 111122223333.dkr.ecr.ap-northeast-1.amazonaws.com/hello-world:latest
リポジトリURIにecr-fipsが含まれる場合は使えません。LambdaはコンテナイメージについてECRのFIPSエンドポイントをサポートしていないためです。
pushが終わったら関数を作成します。--package-type Imageと--code ImageUriの組み合わせで、タグまで含めて指定します。
aws lambda create-function \
--function-name hello-world \
--package-type Image \
--code ImageUri=111122223333.dkr.ecr.ap-northeast-1.amazonaws.com/hello-world:latest \
--role arn:aws:iam::111122223333:role/lambda-ex
arm64で動かす場合は--architectures arm64を追加します。既定はx86_64なので、arm64向けにビルドしたイメージを既定のまま登録するとアーキテクチャ不一致となり、関数の登録または呼び出しで失敗します。
同一タグ再pushで関数が切り替わらないdigest解決の罠
ここが最も踏みやすい落とし穴です。Lambdaは関数作成時にイメージタグをdigestへ解決して保持します。そのため、同じ:latestタグに新しいイメージをpushしても、関数は古いdigestを参照し続けます。
コードを更新したときは、イメージを再ビルドしてpushしたうえで、必ずupdate-function-codeを実行してください。タグ文字列が変わっていなくても必要です。
aws lambda update-function-code \
--function-name hello-world \
--image-uri 111122223333.dkr.ecr.ap-northeast-1.amazonaws.com/hello-world:latest \
--publish
CI/CDを組むなら、この挙動を前提に:latestではなくコミットハッシュをタグにするほうが安全です。デプロイした版とECR上のイメージが1対1で対応し、ロールバック時にどのdigestへ戻すかが自明になります。
ECR側に必要な権限
関数を作成するユーザーまたはロールには、ecr:GetRepositoryPolicy、ecr:SetRepositoryPolicy、ecr:BatchGetImage、ecr:GetDownloadUrlForLayerが必要です。同一アカウント内なら、実行ロールのIDベースポリシーかECRリポジトリのリソースベースポリシーのどちらか一方が許可していれば通ります。クロスアカウントでは両側が必要で、さらにリポジトリポリシーにlambda.amazonaws.comを主体とするステートメントを置きます。片側だけ設定して動かない、という詰まり方が起きるのはこの非対称性が理由です。
コンテナイメージ関数にだけ効く運用上の制約
ここまでの手順はZIPとの違いを吸収すれば済みますが、運用フェーズに入ると挙動そのものが変わる項目があります。導入前に把握しておかないと、障害が起きてから気づくことになります。
廃止通知が届かないコンテナ関数の管理責任
ZIPでマネージドランタイムを使っている場合、廃止の180日前にメール、Health Dashboard、Trusted Advisorの3経路で通知が来ます。コンテナイメージの関数にはこれがありません。AWSは共有責任モデルの表で「Deprecation notifications are not available for functions using container images」と明記し、廃止スケジュールの把握をユーザー側の責任と位置づけています。
provided.al2が2026年7月31日に廃止されたことも、コンテナで使っていた人には通知されていない。ベースイメージのタグと廃止日を資産管理表に載せ、四半期ごとに突き合わせる運用を作ってください。ベースイメージの再ビルドと再デプロイも同じくユーザー側の責任です。
数週間未実行で起きるInactive遷移と初回呼び出し拒否
コンテナイメージをアップロードすると、Lambdaは呼び出し可能になる前にイメージを最適化します。この間、関数はPending状態で、完了するとActiveになります。ここまでは初回だけの話です。
問題はその先です。数週間にわたって呼び出しがないとLambdaは最適化済みイメージを回収し、関数をInactiveにします。この状態で呼び出すと、Lambdaは最初のリクエストを拒否し、再最適化のためにPendingへ移ります。低頻度でしか動かないバッチや、めったに使わない管理系の関数では、久しぶりの実行が必ず1回落ちる。呼び出し側にリトライを持たせるか、定期的なウォームアップ実行を仕込むかの判断が要ります。ZIPの関数にこの状態遷移はありません。
ECRからイメージが消えていたり権限が剥がれていたりすると、関数はFailedになり以降のすべての呼び出しが失敗します。ECRのライフサイクルポリシーで古いイメージを削除している環境では、稼働中の関数が参照しているdigestを消さないよう除外ルールを入れてください。状態遷移や失敗の検知方法はLambdaをCloudWatchで監視する構成とCloudWatch Logsを使ったログ運用で扱っています。
SnapStart非対応下でのコールドスタート対策
コールドスタート対策としてSnapStartを検討する場合、コンテナイメージは選択肢から外れます。SnapStartが対応するのはJava 11以降、Python 3.12以降、.NET 8以降のマネージドランタイムだけで、OS専用ランタイムとコンテナイメージはいずれも非対応です。Goで書いている時点でSnapStartの対象外であり、コンテナ化するかどうかとは別の理由で使えません。
したがって、Go+コンテナイメージでコールドスタートを詰める手段はProvisioned Concurrencyとバイナリ・イメージの縮小に絞られます。-tags lambda.norpcとマルチステージビルドで最終イメージを小さく保つ意味は、ここにもある。SnapStartの仕組みそのものはLambda SnapStartの基本的な特徴で解説しています。
マルチアーキイメージ非対応という制約
Lambdaはx86_64とarm64の両方をサポートしますが、マルチアーキテクチャのコンテナイメージを使う関数はサポート対象外です。両方に配るなら、イメージを別々にビルドして関数も分けて作ることになります。前述の--provenance=falseが必須なのも、この制約に由来します。
単一アーキテクチャになっているかは、pushする前にdocker buildx imagetools inspect docker-image:testで確認できます。単一のマニフェストが返ればLambdaに登録できます。image index が返ってunknown/unknownのプラットフォームが並んでいたら、--provenance=falseの付け忘れです。
日本語フォントなどOSパッケージを同梱する実装
コンテナイメージを選ぶ実務上の動機として大きいのが、OSレベルの依存を同梱できる点です。代表例が日本語フォントで、PDFや画像を生成する関数では文字が豆腐になる問題に必ずぶつかります。AL2023リポジトリのgoogle-noto-sans-cjk-jp-fontsはインストール後で約110MBあり、これだけでZIPの250MB枠の半分近くを占めます。コンテナイメージなら、先ほどのDockerfileの最終ステージに1行足すだけです。
FROM public.ecr.aws/lambda/provided:al2023
RUN dnf install -y google-noto-sans-cjk-jp-fonts fontconfig && dnf clean all
COPY --from=build /helloworld/main ./main
ENTRYPOINT [ "./main" ]
fontconfigを明示している点が要注意です。google-noto-sans-cjk-jp-fontsが依存として要求するのはgoogle-noto-cjk-fonts-commonだけで、fontconfigは引いてきません。provided.al2023はAL2023 minimalベースでfontconfigを含まないため、フォントファイルは置かれてもアプリケーション側から名前で解決できない状態になります。
provided.al2023のパッケージマネージャはmicrodnfの実装がdnfとしてシンボリックリンクされたものです。依存解決は行われますが、プラグインやモジュール管理といったフル機能版のdnfにしかない機能は使えません。それらに依存するインストール手順は書き換えが必要です。
フォントが入ったかは、ハンドラを経由せずシェルで確認できます。docker run --rm --entrypoint sh docker-image:test -c "fc-list | grep -i noto"でNotoのファイルが列挙されれば準備完了です。dnf clean allを同じRUNに含めるのは、キャッシュがレイヤーに残ってイメージサイズを押し上げるためです。10GBの枠があるとはいえ、イメージが大きいほど最適化とECR転送に時間がかかります。
よくある質問
Lambdaのgo1.xランタイムはまだ使えますか
go1.xは2024年1月8日に廃止され、2024年2月8日以降は新規作成もできません。既存関数の更新も2027年3月3日にブロックされます。現在Goで関数を作る手段は、ZIPでprovided.al2023ランタイムを使うか、コンテナイメージを使うかの2択です。
コンテナ内でファイルを書き込めますか
イメージのファイルシステムは読み取り専用です。書き込めるのは/tmpだけで、容量は512MBから10,240MBまで1MB単位で設定できます。中間ファイルを作る処理は出力先を/tmpに寄せてください。
コンテナイメージにするとコールドスタートは遅くなりますか
Lambdaはアップロード時にイメージを最適化してからActiveにするため、イメージサイズがそのまま初回起動時間になるわけではありません。サイズが効くのはむしろ、デプロイのたびに発生するECRへの転送と最適化の待ち時間です。実行側の起動遅延を詰めたい場合、SnapStartが使えないためProvisioned Concurrencyで必要な同時実行数ぶんの環境を常駐させることになります。
ZIPで作った既存の関数をコンテナイメージに切り替えられますか
できません。デプロイパッケージの形式は既存関数では変更不可のため、新規に関数を作成します。エイリアスやイベントソースマッピング、関数URLも作り直しになるので、移行作業として計画してください。
arm64(Graviton)で動かすには何を変えればよいですか
本文で触れた3か所をまとめると、ビルド時の--platform、RIE実行時の--platform、create-functionの--architectures arm64です。既定がx86_64のため、3番目を忘れるとアーキテクチャ不一致になります。