Guide · Lambda

Test AWS Lambda locally with real containers

HomeCloud runs each Lambda function in AWS's official runtime image, as a Docker container on your machine. You create and invoke functions with the AWS CLI or boto3 exactly as on AWS, wire them to SQS queues, and read their output in CloudWatch Logs.

HomeCloud v0.4.0Updated 2026-10-118 min read

There are two common ways to test Lambda code without deploying it. You can call the handler directly in a unit test, which is fast but skips the runtime, the event shape and permissions. Or you can run something that behaves like the Lambda service. HomeCloud is the second kind: the Lambda API is implemented in HomeCloud, and the function runs inside public.ecr.aws/lambda/python:3.12 (or whichever runtime you choose), the same base image AWS publishes for container-image functions. Your handler sees the real runtime, the real event and context objects, and real environment variables.

1. Start HomeCloud

shell
curl -fsSL https://homecloud.pages.dev/scripts/install.sh | sh
homecloud serve &
eval "$(homecloud aws-env)"    # AWS_ENDPOINT_URL, keys and region for the AWS CLI and SDKs

Supported runtimes in v0.4.0: Python 3.9 to 3.13, Node.js 18, 20 and 22, Java 17 and 21, Ruby 3.3, .NET 8, and the OS-only runtimes provided.al2023 and provided.al2 for Go, Rust or any binary that implements the runtime API. Container-image functions work too.

2. Create a function

A minimal Python handler:

lambda_function.py
import json

def lambda_handler(event, context):
    print("event:", json.dumps(event))
    name = event.get("name", "world")
    return {"statusCode": 200, "body": f"Hello, {name}!"}

The AWS CLI requires an execution role, as it does on AWS. HomeCloud checks that the role exists, that its trust policy allows lambda.amazonaws.com, and that you may pass it (iam:PassRole):

shell
zip function.zip lambda_function.py

ROLE=$(aws iam create-role --role-name hello-role \
  --assume-role-policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow",
    "Principal":{"Service":"lambda.amazonaws.com"},"Action":"sts:AssumeRole"}]}' \
  --query Role.Arn --output text)

aws lambda create-function --function-name hello \
  --runtime python3.12 --handler lambda_function.lambda_handler \
  --role "$ROLE" --zip-file fileb://function.zip

Inside the container the function gets temporary credentials for its role and AWS_ENDPOINT_URL pointing back at HomeCloud, so boto3 or the JavaScript SDK called from the handler reaches your local S3, DynamoDB or SQS without code changes. The role's policies are enforced on those calls.

3. Invoke it

From the AWS CLI, with the last 4 KB of the log returned in the response:

shell
aws lambda invoke --function-name hello \
  --cli-binary-format raw-in-base64-out --payload '{"name":"dev"}' \
  --log-type Tail --query LogResult --output text out.json | base64 --decode
cat out.json      # {"statusCode": 200, "body": "Hello, dev!"}

The first invoke pulls the runtime image, which takes a while once. After that, an environment stays warm and is reused for later invokes, and is removed after 15 idle minutes. A code or configuration change replaces it, as on AWS.

From a test, boto3 1.28 and later read AWS_ENDPOINT_URL, so nothing in the client needs to change:

test_hello.py
import json, boto3

def test_hello():
    lam = boto3.client("lambda")     # endpoint, keys and region from the environment
    resp = lam.invoke(FunctionName="hello", Payload=json.dumps({"name": "ci"}))
    body = json.load(resp["Payload"])
    assert body["statusCode"] == 200

To start HomeCloud from the test itself, the testcontainers-python module gives you a session fixture; homecloud.get_client("lambda") returns a client already pointed at it. There is a Go module too.

Versions, aliases (--function-name hello:live), layers, async invokes, destinations, reserved concurrency and function URLs are supported. API Gateway HTTP APIs can proxy to a function with payload format 1.0 or 2.0.

4. Trigger it from SQS

Event source mappings poll a queue and invoke the function with batches, as on AWS:

shell
QURL=$(aws sqs create-queue --queue-name jobs --query QueueUrl --output text)
QARN=$(aws sqs get-queue-attributes --queue-url "$QURL" \
  --attribute-names QueueArn --query Attributes.QueueArn --output text)

aws lambda create-event-source-mapping --function-name hello \
  --event-source-arn "$QARN" --batch-size 10 \
  --function-response-types ReportBatchItemFailures

aws sqs send-message --queue-url "$QURL" --message-body '{"name":"queue"}'

Two differences from AWS to know about. The batch size is capped at 10 for SQS sources. And HomeCloud checks sqs:ReceiveMessage and sqs:DeleteMessage against the identity that creates the mapping, while AWS checks the function's execution role; grant the role those permissions anyway so the same setup works when you deploy. Failed messages follow the queue's redrive policy to its dead-letter queue. DynamoDB streams can trigger functions the same way; Kinesis and other sources are not available.

5. Read the logs

Everything the function prints goes to CloudWatch Logs, in the group /aws/lambda/<name> with one stream per environment:

shell
aws logs tail /aws/lambda/hello --since 10m
aws logs filter-log-events --log-group-name /aws/lambda/hello --filter-pattern '"event:"'

The web console at 127.0.0.1:8080 shows the same logs next to the function, with invocation metrics, an in-browser code editor and a test-event button. Logs Insights queries work on these groups as well. You can look around a demo of the Lambda console without installing anything.

Where it differs from AWS Lambda

  • Functions run on your Docker host, so cold starts depend on your machine and on whether the image is cached. Do not use HomeCloud for performance numbers.
  • Memory and timeout settings are applied to the environment, but there is no billing, no account concurrency quota and one region.
  • Event sources are SQS and DynamoDB streams. API Gateway REST and WebSocket APIs are not supported; HTTP APIs are.

The full list of supported operations is in docs/aws-compat.md. To deploy the same functions with Terraform, see running Terraform against a local AWS; to keep a Lambda-capable server running at home, see self-hosting S3, IAM and Lambda.