Lambda
LocalEmu Lambda executes your function code in real Docker containers using the official AWS Lambda runtime images from public.ecr.aws/lambda. State persists across restarts when PERSISTENCE=1. Cold-start, container reuse, layers, versioning, aliases, VPC configuration, and event-source mappings all work end-to-end.
Coverage at the operation level: see the Lambda coverage matrix for which of the 85 Lambda operations are implemented.
Quick start
Create a function from a zip
$ cat > handler.py <<'EOF'
def lambda_handler(event, context):
return {"statusCode": 200, "body": "hello from LocalEmu"}
EOF
$ zip handler.zip handler.py
$ awsemu lambda create-function \
--function-name hello \
--runtime python3.12 \
--role arn:aws:iam::000000000000:role/lambda-role \
--handler handler.lambda_handler \
--zip-file fileb://handler.zip
FunctionArn: arn:aws:lambda:us-east-1:000000000000:function:hello
State: Active
Runtime: python3.12 No build step on LocalEmu's side. The zip is loaded into the runtime container at invocation time.
Invoke synchronously
$ awsemu lambda invoke \
--function-name hello \
--payload '{}' \
/tmp/out.json
StatusCode: 200
$ cat /tmp/out.json
{"statusCode": 200, "body": "hello from LocalEmu"} Invoke asynchronously
$ awsemu lambda invoke \
--function-name hello \
--invocation-type Event \
--payload '{"k":"v"}' \
/tmp/out.json
StatusCode: 202 Async invocations return 202 immediately, the queue dispatches to a container in the background, and retries follow AWS's exponential backoff (configurable, see LAMBDA_RETRY_BASE_DELAY_SECONDS).
Supported runtimes
Image format: public.ecr.aws/lambda/<family>:<version>. The runtime identifier you pass to --runtime maps to a tagged image in AWS's public ECR gallery. The image is pulled on first use and cached locally.
| Family | Active runtimes | Deprecated (best-effort) |
|---|---|---|
nodejs | nodejs24.x, nodejs22.x, nodejs20.x, nodejs18.x, nodejs16.x | nodejs14.x, nodejs12.x |
python | python3.14, python3.13, python3.12, python3.11, python3.10, python3.9, python3.8 | python3.7 |
java | java25, java21, java17, java11, java8.al2 | java8 |
dotnet | dotnet10, dotnet8, dotnet6 | dotnetcore3.1 |
ruby | ruby3.4, ruby3.3, ruby3.2 | ruby2.7 |
go | (use provided.al2023 for Go) | go1.x |
provided | provided.al2023, provided.al2 | provided |
Container image functions (--package-type Image) bypass this mapping and run your image directly. Pull the runtime images ahead of time with LAMBDA_PREBUILD_IMAGES=1 if you want to avoid the first-invocation wait.
Executor architecture
LocalEmu ships one runtime executor: docker. Selected by LAMBDA_RUNTIME_EXECUTOR (default: docker). The executor spins up a container per cold start, keeps it warm between invocations, and tears it down after LAMBDA_KEEPALIVE_MS of idle time (default 10 minutes).
Container lifecycle:
- •Cold start: container does not exist, runtime image is pulled if missing, container is created, init runs.
- •Warm reuse: subsequent invocations within
LAMBDA_KEEPALIVE_MSreuse the same container. - •Idle teardown: container is stopped and removed after the keepalive window expires.
- •Concurrent invocations: a second container is spun up if all warm containers are busy, up to
LAMBDA_LIMITS_CONCURRENT_EXECUTIONS.
Containers expose AWS_ENDPOINT_URL in the environment so boto3 inside your function reaches LocalEmu without any extra wiring. Unset this with LAMBDA_DISABLE_AWS_ENDPOINT_URL=1 if you need the function to use a different endpoint.
Limits and defaults
| Limit | Default | Source |
|---|---|---|
| Function timeout | 3 s (max 900 s) | AWS Lambda limit |
| Memory | 128 MB (max 10240 MB) | AWS Lambda limit |
| Sync invoke payload | 6 MB | LAMBDA_LIMITS_MAX_FUNCTION_PAYLOAD_SIZE_BYTES |
| Function code (zipped) | 50 MB | LAMBDA_LIMITS_CODE_SIZE_ZIPPED |
| Function code (unzipped) | 250 MB | LAMBDA_LIMITS_CODE_SIZE_UNZIPPED |
| Layers per function | 5 | AWS Lambda limit |
| Env vars per function | 4 KiB total | LAMBDA_LIMITS_MAX_FUNCTION_ENVVAR_SIZE_BYTES |
| Concurrent executions (account) | 1000 | LAMBDA_LIMITS_CONCURRENT_EXECUTIONS |
| Total code storage (account) | ~75 GB | LAMBDA_LIMITS_TOTAL_CODE_SIZE |
All limit env vars accept a raw byte count, so LAMBDA_LIMITS_CODE_SIZE_ZIPPED=104857600 raises the zipped-code limit to 100 MB.
Configuration
The full set of Lambda environment variables, grouped by purpose. Defaults are the values used when the variable is unset.
Executor and runtime images
| Variable | Default | Purpose |
|---|---|---|
LAMBDA_RUNTIME_EXECUTOR | docker | Runtime executor plugin. One option ships today: docker. |
LAMBDA_KEEPALIVE_MS | 600000 | Idle window (ms) before a warm container is torn down. |
LAMBDA_REMOVE_CONTAINERS | true | Whether to delete containers when they're stopped. Set false to keep them for inspection. |
LAMBDA_PREBUILD_IMAGES | false | Pre-pull runtime images at LocalEmu startup. Avoids first-invocation pull latency. |
LAMBDA_RUNTIME_IMAGE_MAPPING | (none) | JSON map overriding the runtime identifier to image mapping. Useful for air-gapped clones of the AWS images. |
LAMBDA_RUNTIME_VALIDATION | 0 | When 1, validate that the runtime image exists before invocation. |
LAMBDA_IGNORE_ARCHITECTURE | false | Skip architecture (x86_64/arm64) checks. Useful when running arm64 images on x86 hosts. |
Container and networking
| Variable | Default | Purpose |
|---|---|---|
LAMBDA_DOCKER_NETWORK | (none) | Docker network to attach Lambda containers to. Set this to your Compose network so containers can reach other services. |
LAMBDA_DOCKER_FLAGS | (none) | Extra flags passed to docker run. Use for capabilities, volume mounts, etc. |
LAMBDA_DISABLE_AWS_ENDPOINT_URL | false | Stop injecting AWS_ENDPOINT_URL into the function environment. |
Cold start and timing
| Variable | Default | Purpose |
|---|---|---|
LAMBDA_COLD_START_DELAY | 0 | Artificial cold-start delay (seconds). Use to test cold-start handling in your code. |
LAMBDA_COLD_START_IDLE_TIMEOUT | 300 | Idle timeout (seconds) before a container is considered cold again. Independent of LAMBDA_KEEPALIVE_MS. |
LAMBDA_RUNTIME_ENVIRONMENT_TIMEOUT | 20 | Seconds to wait for the runtime container to come up before failing the invocation. |
Account-wide limits
| Variable | Default (bytes / count) |
|---|---|
LAMBDA_LIMITS_CONCURRENT_EXECUTIONS | 1000 |
LAMBDA_LIMITS_MINIMUM_UNRESERVED_CONCURRENCY | 100 |
LAMBDA_LIMITS_TOTAL_CODE_SIZE | 80530636800 |
LAMBDA_LIMITS_CODE_SIZE_ZIPPED | 52428800 |
LAMBDA_LIMITS_CODE_SIZE_UNZIPPED | 262144000 |
LAMBDA_LIMITS_CREATE_FUNCTION_REQUEST_SIZE | 70167211 |
LAMBDA_LIMITS_MAX_FUNCTION_ENVVAR_SIZE_BYTES | 4096 |
LAMBDA_LIMITS_MAX_FUNCTION_PAYLOAD_SIZE_BYTES | 6291300 |
Event source mapping
| Variable | Default | Purpose |
|---|---|---|
LAMBDA_EVENT_SOURCE_MAPPING_POLL_INTERVAL_SEC | 1 | How often the poller checks each event source. |
LAMBDA_EVENT_SOURCE_MAPPING_MAX_BACKOFF_ON_ERROR_SEC | 60 | Max backoff (seconds) when an event source returns errors. |
LAMBDA_EVENT_SOURCE_MAPPING_MAX_BACKOFF_ON_EMPTY_POLL_SEC | 10 | Max backoff (seconds) when the source has no events. |
Retries, streams, and create timing
| Variable | Default | Purpose |
|---|---|---|
LAMBDA_RETRY_BASE_DELAY_SECONDS | 60 | Base delay between async-invoke retries (seconds). |
LAMBDA_TRUNCATE_STDOUT | 2000 | Truncate function stdout to this many characters in logs. |
LAMBDA_SYNCHRONOUS_CREATE | false | Block CreateFunction until the runtime image is pulled. Useful in CI. |
Debug and init binary
The init binary is the entrypoint LocalEmu injects into runtime containers (the same role AWS's aws-lambda-rie plays). These variables override its path, version, or runtime behaviour.
| Variable | Default | Purpose |
|---|---|---|
LAMBDA_DEBUG_MODE | false | Enable verbose Lambda-service logging. |
LAMBDA_DEBUG_MODE_CONFIG_PATH | (none) | Path to a debug config file. |
LAMBDA_INIT_DEBUG | false | Verbose logging from the init binary inside the container. |
LAMBDA_INIT_BIN_PATH | (bundled) | Override the init binary path on the host. |
LAMBDA_INIT_BOOTSTRAP_PATH | (bundled) | Override the bootstrap script path. |
LAMBDA_INIT_RELEASE_VERSION | (latest) | Pin the init binary release version. |
LAMBDA_INIT_USER | (container default) | Run the init binary as a specific UID/user. |
LAMBDA_INIT_POST_INVOKE_WAIT_MS | (none) | Sleep (ms) inside the init binary after each invocation. Useful for log-flush races. |
LAMBDA_INIT_DELVE_PATH | (none) | Path to the Delve debugger binary, for attaching to Go runtimes. |
LAMBDA_INIT_DELVE_PORT | 40000 | Port the Delve debugger listens on inside the container. |
Features supported
| Feature | Notes |
|---|---|
| Zip and container-image packages | Both --package-type Zip and --package-type Image work. Container images can be pulled from LocalEmu's ECR. |
| Versioning and aliases | publish-version, create-alias, traffic shifting via routing config. |
| Layers | Up to 5 layers per function. Cross-account fetch is not supported (see Known limitations). |
| Function URLs | create-function-url-config exposes the function over HTTP at the gateway. |
| Provisioned and reserved concurrency | Concurrency limits are enforced against LAMBDA_LIMITS_CONCURRENT_EXECUTIONS. |
| Dead-letter queues | SQS and SNS DLQs on async invoke. |
| Environment variables, tags | Both per-function and per-version. |
| X-Ray tracing | Enabled when TracingMode is set to Active. |
| SnapStart | Stored on the function version. Cold-start delay setting drives the simulated restore time. |
| Code signing | CodeSigningConfig CRUD is implemented. |
Examples
Container-image function
$ awsemu ecr create-repository --repository-name my-fn
$ docker build -t my-fn .
$ docker tag my-fn 000000000000.dkr.ecr.us-east-1.localhost:4566/my-fn:latest
$ docker push 000000000000.dkr.ecr.us-east-1.localhost:4566/my-fn:latest
$ awsemu lambda create-function \
--function-name my-fn \
--package-type Image \
--role arn:aws:iam::000000000000:role/lambda-role \
--code ImageUri=000000000000.dkr.ecr.us-east-1.localhost:4566/my-fn:latest
PackageType: Image
State: Active Publish a version, attach an alias
$ awsemu lambda publish-version --function-name hello
Version: 1
$ awsemu lambda create-alias \
--function-name hello \
--name prod \
--function-version 1
AliasArn: arn:aws:lambda:us-east-1:000000000000:function:hello:prod
$ awsemu lambda invoke \
--function-name hello:prod \
--payload '{}' \
/tmp/out.json
StatusCode: 200 Publish a layer, attach to the function
$ mkdir -p python && pip install requests -t python/
$ zip -r layer.zip python
$ awsemu lambda publish-layer-version \
--layer-name shared-deps \
--zip-file fileb://layer.zip \
--compatible-runtimes python3.12 python3.11
LayerVersionArn: arn:aws:lambda:us-east-1:000000000000:layer:shared-deps:1
$ awsemu lambda update-function-configuration \
--function-name hello \
--layers arn:aws:lambda:us-east-1:000000000000:layer:shared-deps:1 SQS event source mapping
$ awsemu sqs create-queue --queue-name jobs
$ awsemu lambda create-event-source-mapping \
--function-name hello \
--event-source-arn arn:aws:sqs:us-east-1:000000000000:jobs \
--batch-size 10
UUID: 8a7b6c5d-...
State: Enabled
$ awsemu sqs send-message \
--queue-url http://sqs.us-east-1.localhost:4566/000000000000/jobs \
--message-body '{"work":"item-1"}'
# Lambda is invoked asynchronously by the poller.
$ awsemu logs filter-log-events \
--log-group-name /aws/lambda/hello \
--query 'events[].message' SQS, Kinesis, and DynamoDB Streams have dedicated pollers in services/lambda_/event_source_mapping/pollers/. Each runs in a background thread and respects the LAMBDA_EVENT_SOURCE_MAPPING_* backoff variables. Kafka (MSK and self-managed) accepts the same create-event-source-mapping call but dispatches via EventBridge Pipes under the hood.
Known limitations
Twenty of the 85 Lambda operations return NotImplementedException (501). Grouped by feature area:
- •Durable Executions (8 ops):
GetDurableExecution,GetDurableExecutionHistory,GetDurableExecutionState,ListDurableExecutionsByFunction,SendDurableExecutionCallbackFailure,SendDurableExecutionCallbackHeartbeat,SendDurableExecutionCallbackSuccess,StopDurableExecution. - •Capacity Providers (7 ops):
CheckpointDurableExecution,CreateCapacityProvider,DeleteCapacityProvider,GetCapacityProvider,ListCapacityProviders,ListFunctionVersionsByCapacityProvider,UpdateCapacityProvider. - •Scaling and runtime management config (4 ops):
GetFunctionScalingConfig,PutFunctionScalingConfig,GetRuntimeManagementConfig,PutRuntimeManagementConfig. - •Response streaming:
InvokeWithResponseStreamis not implemented.
Other behavioural caveats:
- •VPC config is stored on the function but security-group and subnet enforcement is not applied at the network layer. Functions can reach any host the LocalEmu process can reach.
- •EFS file system mounts are not supported. Use bind-mounted host directories via
LAMBDA_DOCKER_FLAGSif you need shared file storage. - •Cross-account layer fetching is not supported even when a role assumption would allow it on AWS.
- •Concurrent function-modification operations (publishing a version while updating configuration) have a known race condition. Serialise these calls in your tests.
The exact set of operations and their status is in the Lambda coverage matrix.