lambda_deps_builder
A CDK construct that builds Python Lambda dependencies inside an actual AWS Lambda during cdk deploy — no local Docker, no platform mismatch, no cross-compile gymnastics.
What it solves
Building Python Lambda packages on Windows or on a developer machine without Docker produces wheels that fail on Lambda's Linux runtime. The standard CDK answers all have caveats:
| Approach | Where deps are resolved | Local prerequisites | Cross-platform safe? |
|---|---|---|---|
lambda.Code.from_asset(local_pip_install_dir) |
Dev machine | Python + pip | No — native wheels often wrong |
aws_lambda_python_alpha.PythonFunction |
Local Docker container | Docker daemon | Yes (Docker does the cross-build) |
aws_lambda_nodejs.NodejsFunction |
Local esbuild (or Docker fallback) | Node.js + esbuild (or Docker) | Yes (JS is mostly platform-agnostic) |
LambdaDepsBuilder (this POC) |
Inside AWS Lambda, during cdk deploy |
None beyond CDK | Yes — by definition |
The build runs on the same Lambda runtime + architecture that will execute the deps, so pip selects matching wheels automatically.
Architecture
synth ──> CDK creates: S3 bucket
+ TriggerFunction(runtime=python3.12, architecture=X)
+ LayerVersion(code=fromBucket, compatible_architectures=[X])
deploy ──> 1. CFN creates bucket
2. CFN creates the TriggerFunction (handler asset bundles requirements.txt)
3. Triggers framework invokes the function ONCE, synchronously, on AWS Lambda
└─ handler runs ON ARCHITECTURE X:
pip install -r requirements.txt -t /tmp/build/python
zip /tmp/build → /tmp/deps.zip
s3.put_object(Bucket=..., Key=deps-X.zip)
4. CFN creates LayerVersion (depends_on the trigger) → reads zip from S3
5. Consumer Function(architecture=X, layers=[layer]) runs with matching deps
The trigger re-fires whenever requirements.txt changes (its content is part of the staged asset hash) or whenever the architecture changes.
Usage
from pathlib import Path
from aws_cdk import Stack, Duration
from aws_cdk import aws_lambda as lambda_
from lambda_deps_builder import LambdaDepsBuilder
class MyStack(Stack):
def __init__(self, scope, id, **kwargs):
super().__init__(scope, id, **kwargs)
builder = LambdaDepsBuilder(
self, "Deps",
requirements_txt_file=Path(__file__).parent / "requirements.txt",
target_runtime=lambda_.Runtime.PYTHON_3_12,
target_architecture=lambda_.Architecture.ARM_64, # or X86_64
)
layer = builder.as_layer_version(self, "DepsLayer")
lambda_.Function(
self, "Fn",
runtime=lambda_.Runtime.PYTHON_3_12,
architecture=lambda_.Architecture.ARM_64,
handler="handler.handler",
code=lambda_.Code.from_asset("./my_handler"),
layers=[layer],
timeout=Duration.seconds(15),
)
That's it — cdk deploy, and the consumer Function imports its dependencies on the next invocation.
Architecture support
Pass target_architecture=lambda_.Architecture.ARM_64 for Graviton consumers. The construct:
- runs the build Lambda on the chosen architecture, so
pippicksaarch64wheels for compiled extensions likepydantic-core,cryptography,numpy; - sets
compatible_architectures=[chosen]on the LayerVersion so Lambda rejects mismatched consumers at attach time (instead of a confusing runtimeImportError); - defaults
output_keytodeps-{ARCH}.zipso two builders sharing one bucket don't overwrite each other.
The example stack builds for both x86_64 and arm64 off a shared bucket — see lambda_deps_builder/example/example_stack.py.
Why no Node.js variant?
aws_cdk.aws_lambda_nodejs.NodejsFunction already handles the JS case adequately:
- it uses esbuild to bundle handler code + tree-shaken deps into a single asset;
- if esbuild is on the dev machine, the entire bundle (including
npm installofbundling.nodeModules) runs locally with no Docker; - if esbuild is missing, it transparently falls back to bundling inside a local Docker container based on the Node.js Lambda image;
- JS deps are overwhelmingly pure JS, so platform mismatch is rarely an issue. For the rare native module (
sharp), the Docker fallback handles it.
So the JS pain point this POC would address (Linux-native compiled deps) is largely absent in Node.js and well-handled by NodejsFunction for the few cases where it matters. Use NodejsFunction for JS Lambdas.
Running the example
cd lambda_deps_builder
poetry install
poetry run cdk deploy
# invoke either consumer to confirm `requests` is importable on Lambda:
aws lambda invoke \
--function-name "$(aws cloudformation describe-stacks \
--stack-name LambdaDepsBuilderExample \
--query "Stacks[0].Outputs[?OutputKey=='ConsumerX86FunctionName'].OutputValue" \
--output text)" \
/tmp/out.json && cat /tmp/out.json
poetry run cdk destroy
Running tests
poetry install
poetry run pytest -v # fast tests: synth + handler unit tests, no AWS
poetry run pytest -v -m e2e # the real-AWS E2E test (deploys, invokes, destroys)
poetry run pytest -v -m '' # everything (default suite + e2e)
pyproject.toml sets addopts = -m 'not e2e' so a plain pytest skips the E2E test by default.
The default suite is pure-Python (CDK assertion templates + mocked handler) — no AWS, no Docker.
The E2E test (tests/test_e2e_deploy.py):
- requires the
cdkCLI onPATH, AWS credentials, and the target account/region bootstrapped (cdk bootstraponce); - deploys a uniquely-named stack via
cdk deploy --require-approval never; - invokes both consumer Lambdas (x86_64 and arm64) and asserts each returns
{"status": 200, ...}— provingrequestswas built on Linux Lambda and is importable from a separate consumer Lambda; - destroys the stack in a
finallyblock so a failed deploy or assertion still cleans up; - takes ~2–3 minutes per run.
Caveats
- Lambda layer size limit (250 MB unzipped) —
pandas+numpytogether exceed this. For large dep sets, use a container image Lambda instead. /tmpsize — defaults to 0.5 GiB on Lambda; raiseephemeral_storage_gibfor big trees.- First-deploy latency — the trigger invocation adds ~30–60 s to the first deploy (and to any deploy where requirements changed).
- Cost is negligible — a one-shot Lambda invocation per deploy and a tiny S3 object.
- Two architectures = two builders — cheap, but the example shows the pattern explicitly.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file lambda_deps_builder-0.1.0.tar.gz.
File metadata
- Download URL: lambda_deps_builder-0.1.0.tar.gz
- Upload date:
- Size: 7.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c925d22f31b7db9a13853a11f541aab6d6b272d922cb9ee06f7cb480198b5837
|
|
| MD5 |
695663a5fe03d7a4660c36ab7553a2b2
|
|
| BLAKE2b-256 |
506650bac56209e01a6004abe51a42e7b68327201cbaf7fdb1cab60edb3bb1d5
|
File details
Details for the file lambda_deps_builder-0.1.0-py3-none-any.whl.
File metadata
- Download URL: lambda_deps_builder-0.1.0-py3-none-any.whl
- Upload date:
- Size: 10.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
20b5819a8cc0ff6a19f83222532de80120ad1f6d9ae66384f98a7243df8d8417
|
|
| MD5 |
8cadb5fdd16fa1d3f2a6a93a6233da92
|
|
| BLAKE2b-256 |
12980d999be830b553e72d9230b786d0b81113f55a22da603fb6c955bfb07d24
|