Local Lambda Build
The default deploy path builds Lambda layer zips and processor container
images using AWS CodeBuild. That works well in CI and for hermetic
deploys but adds 5–15 minutes to every terraform apply and costs
real money in CodeBuild minutes. When you're iterating on Lambda
code locally, the round-trip is painful.
The build variable lets you opt into building those artifacts on
your machine instead, using Docker, Podman, or Finch.
build = {
lambda_local = true # build locally
lambda_architecture = "x86_64" # or "arm64"
container_runtime = "auto" # auto-detect docker/podman/finch
}
With lambda_local = true:
- All
aws_codebuild_projectresources, the CodeBuild trigger Lambdas, their IAM roles, log groups, and the buildspec source S3 objects collapse tocount = 0. No CodeBuild infrastructure is provisioned. - Lambda layer zips are built inside the AWS SAM build image
(
public.ecr.aws/sam/build-python3.12:latest-<arch>) on your host. - Processor container images (BDA and SageMaker UDOP) are built and
pushed to ECR via
docker buildx build --push.
The build flag is non-breaking. The default is lambda_local = false,
which preserves the historical CodeBuild path bit-for-bit.
When to use which path
| Situation | Recommended setting |
|---|---|
| Daily Lambda dev loop on a workstation | lambda_local = true |
| Production / CI deploy | lambda_local = false (CodeBuild default) |
| Air-gapped or proxied environment without Docker | lambda_local = false |
| Compliance auditor wants hermetic AWS-managed builds | lambda_local = false |
| You want arm64 Lambdas | lambda_architecture = "arm64" (works on either path) |
The architecture flag is honored by both paths — flipping to arm64 on the
CodeBuild path switches the CodeBuild image to
aws/codebuild/amazonlinux2-aarch64-standard:3.0 and tags the layer with
compatible_architectures = ["arm64"].
Prerequisites
You need exactly one container runtime on your deploy host. The wrapper
auto-detects in this order: Docker → Podman → Finch. Set
build.container_runtime to a specific name ("docker", "podman",
"finch") to force the choice and disable fallback.
macOS
# Docker Desktop (most common)
# https://docs.docker.com/desktop/install/mac-install/
# Podman (alternative)
brew install podman
podman machine init
podman machine start
# Finch (AWS-supported alternative)
brew install --cask finch
finch vm init
finch vm start
Linux
# Docker Engine
# https://docs.docker.com/engine/install/
# Podman
sudo dnf install -y podman # Fedora / RHEL
sudo apt install -y podman # Debian / Ubuntu
# Enable a rootless Docker-compatible socket
systemctl --user enable --now podman.socket
Windows
# Docker Desktop: https://docs.docker.com/desktop/install/windows-install/
# Podman Desktop: https://podman-desktop.io/downloads
# Finch: https://runfinch.com (preview)
If no runtime is detected and lambda_local = true, plan fails with a
clear error from modules/build-runtime-check's check {} block,
including install hints for each platform.
Architecture selection
Set build.lambda_architecture to "arm64" if your Lambdas should run
on Graviton. Graviton Lambdas are ~20% cheaper at the same memory size
and often faster on memory-bound workloads.
Cross-architecture builds
If your deploy host is x86_64 but you want arm64 Lambdas (or vice
versa), the docker daemon needs buildx and a working QEMU emulator.
- macOS Docker Desktop: ships both, no setup needed.
- Linux:
sudo apt install qemu-user-static binfmt-support, thendocker run --privileged tonistiigi/binfmt --install allonce per boot. - Podman/Finch: similar to Docker on Linux; check their docs.
If buildx complains "exec format error" during the local-build path, QEMU emulation isn't set up.
Migration plan
Switching an existing deployment from lambda_local = false to true
(or back) requires no terraform state surgery. The plan will:
- Destroy every
aws_codebuild_project, CodeBuild trigger Lambda, CodeBuild IAM role, CodeBuild CloudWatch log group, and buildspec source S3 object created by the CodeBuild path. - Create the equivalent local-build resources (
null_resourcebuilders, no IAM). - Replace every
aws_lambda_layer_version, because the S3 object key changes between paths. - Update in place every
aws_lambda_functionthat references those layers — only the layer ARN changes, the function itself is preserved. - Preserve every
aws_ecr_repositoryfor processor images — same resource address in both paths.
Lambda hot-swaps layer references on the next cold start, so layer replacement is zero-downtime.
If you have CloudWatch alarms or other observability tied to specific CodeBuild project names, those alarms will break when you flip the flag. Document this as part of your migration plan.
To roll back, flip the flag the other way. The reverse plan will re-create CodeBuild infrastructure and replace layers back to the CodeBuild-produced artifacts.
Troubleshooting
"No usable container runtime detected"
The host doesn't have Docker, Podman, or Finch running. Install one
and try again. If it's installed but not running, start the daemon
(macOS Docker Desktop: open the app; Podman: podman machine start;
Finch: finch vm start).
docker login failed with denied: User ... is not authorized to perform: ecr:GetAuthorizationToken
The deploy host's AWS credentials need ECR access. The local-build
path's null_resource.ecr_login does:
aws ecr get-login-password --region <region> | docker login --username AWS --password-stdin <ecr-url>
Make sure your active profile / role has ecr:GetAuthorizationToken,
ecr:BatchCheckLayerAvailability, ecr:PutImage,
ecr:InitiateLayerUpload, ecr:UploadLayerPart,
ecr:CompleteLayerUpload.
exec format error during docker buildx build
You're cross-building (host arch ≠ Lambda arch) without QEMU emulation set up. See "Cross-architecture builds" above.
Layer build inside the SAM container fails on Pillow
The local-build script copies libjpeg, libpng, libz, libtiff,
libfreetype, liblcms2, libwebp into the layer when it sees
Pillow or PIL in requirements.txt. If Pillow still fails to
import at Lambda runtime, your requirements.txt is likely pinning a
version that doesn't have an aarch64 or x86_64 wheel; either pin to
a version with wheels for both, or remove the pin.
Layer hash differs between machines
The SAM build image gives you the same Python interpreter and pip
version across hosts, so installed-package contents are identical.
What can differ is zip metadata (file mtimes, directory order) which
changes the zip's bytes but not its behaviour. If you need byte-for-byte
reproducibility, normalize that out yourself before comparing —
hashing python/ instead of the zip is the easy answer.
CodeBuild infrastructure didn't fully disappear after switching
aws_codebuild_project and friends should all be destroyed on the
first apply after lambda_local = true. If something lingers, run
terraform plan and inspect — most likely the resource was renamed
upstream and isn't tracked under the same address anymore. The
build_mode output on each layer/processor module will report which
path is actually active.