This Serverless Application Repository (SAR) application publishes a Lambda layer that provides a minimal
custom runtime for provided.al2023 using /bin/sh, plus AWS CLI v2 and jq.
Three SAR applications are published:
lambda-shell-runtime(wrapper that references both architectures)lambda-shell-runtime-arm64lambda-shell-runtime-amd64
The wrapper application publishes no layer itself; it exposes both architecture layer ARNs as stack outputs. Each architecture-specific application publishes a single Lambda layer version.
Deploy the SAR application that matches your needs. After deployment, read the appropriate output and attach it to your function.
Example (wrapper application):
STACK_NAME=lambda-shell-runtime
LAYER_ARN=$(aws cloudformation describe-stacks \
--stack-name "$STACK_NAME" \
--query "Stacks[0].Outputs[?OutputKey=='LayerVersionArnArm64'].OutputValue" \
--output text)For x86_64, use LayerVersionArnAmd64 instead.
Example (single architecture):
STACK_NAME=lambda-shell-runtime-arm64
LAYER_ARN=$(aws cloudformation describe-stacks \
--stack-name "$STACK_NAME" \
--query "Stacks[0].Outputs[?OutputKey=='LayerVersionArn'].OutputValue" \
--output text)- Runtime:
provided.al2023 - Architecture:
arm64orx86_64(must match the layer) - Handler:
function.handler(script stored asfunction.shin your function package)
Lambda automatically includes /opt/bin and /opt/lib from layers in PATH and LD_LIBRARY_PATH.
If you prefer to work entirely in the AWS Lambda console, skip to Lambda console notes below.
- Deploy the SAR application (wrapper or per-arch) and export the layer ARN.
Wrapper (arm64 output example):
STACK_NAME=lambda-shell-runtime
LAYER_ARN=$(aws cloudformation describe-stacks \
--stack-name "$STACK_NAME" \
--query "Stacks[0].Outputs[?OutputKey=='LayerVersionArnArm64'].OutputValue" \
--output text)Per-arch application:
STACK_NAME=lambda-shell-runtime-arm64
LAYER_ARN=$(aws cloudformation describe-stacks \
--stack-name "$STACK_NAME" \
--query "Stacks[0].Outputs[?OutputKey=='LayerVersionArn'].OutputValue" \
--output text)For x86_64, use LayerVersionArnAmd64 (wrapper) or deploy the amd64 application.
- Create a simple handler and package it:
cat > function.sh <<'SH'
#!/bin/sh
handler() {
jq -c '{ok:true, input:.}'
}
SH
zip -r function.zip function.sh- Create an execution role (if you do not already have one):
aws iam create-role \
--role-name lambda-shell-runtime-role \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "lambda.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}'
aws iam attach-role-policy \
--role-name lambda-shell-runtime-role \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
ROLE_ARN=$(aws iam get-role \
--role-name lambda-shell-runtime-role \
--query "Role.Arn" \
--output text)- Create the function with the layer:
aws lambda create-function \
--function-name hello-shell-runtime \
--runtime provided.al2023 \
--handler function.handler \
--architectures arm64 \
--role "$ROLE_ARN" \
--zip-file fileb://function.zip \
--layers "$LAYER_ARN"For x86_64, set --architectures x86_64 and use the amd64 layer ARN.
- Invoke it:
aws lambda invoke \
--function-name hello-shell-runtime \
--payload '{"message":"hello"}' \
response.json
cat response.jsonIf you create a function in the AWS Lambda console, it may show sample files like bootstrap.sh and hello.sh. Those are for a different custom-runtime tutorial and are not used with this layer.
This runtime already provides the bootstrap in the layer. Your function package should only include a handler script (for example, function.sh) that defines a handler function.
Console checklist:
- Runtime:
provided.al2023 - Architecture:
arm64orx86_64(must match the layer you attached) - Handler:
function.handler(script file isfunction.sh, sourced by/bin/sh) - Layers: add the layer ARN from the SAR stack output
- Performance: the AWS CLI layer is large; cold starts can be slow at 128 MB. Raising memory (for example, to 512 MB) and retrying a second invocation yields faster runs.
Example handler file (save as function.sh in the console editor):
#!/bin/sh
handler() {
jq -c '{ok:true, input:.}'
}The runtime sources this file; it does not need to be executable. If you want bash, make the handler file executable,
set a #!/bin/bash shebang, and set the Lambda handler to the filename (no dot).
Your handler is a shell script that defines a function. For example, _HANDLER=function.handler loads
function.sh from LAMBDA_TASK_ROOT and invokes handler with the event JSON on STDIN.
Execution modes:
-
function.handler: the runtime sourcesfunction.shwith/bin/shand invokes thehandlerfunction. The file does not need to be executable and must use POSIXshsyntax. -
handler(no dot): the runtime treats_HANDLERas a handler file path. If the file is executable, it is run directly (shebang honored). If not, the runtime invokes it with/bin/sh. Use this mode for bash by making the handler executable and starting it with#!/bin/bash. -
reads the event JSON from STDIN
-
writes the response JSON to STDOUT
-
writes logs to STDERR
The runtime does not transform the event or response payload.
LAMBDA_RUNTIME_DEADLINE_MS is an epoch time in milliseconds. The helper below returns the remaining time in
milliseconds for POSIX sh handlers.
remaining_time_ms() {
if [ -z "${LAMBDA_RUNTIME_DEADLINE_MS:-}" ]; then
return 1
fi
now_ms=$(date +%s%3N 2>/dev/null || true)
case "$now_ms" in
''|*[!0-9]*)
now_ms=$(( $(date +%s) * 1000 ))
;;
esac
printf '%s\n' "$((LAMBDA_RUNTIME_DEADLINE_MS - now_ms))"
}On AL2023, date supports %s and %3N. If %3N is unavailable, the helper falls back to second precision.
The SAR SemanticVersion matches the bundled AWS CLI v2 version.