Learn LocalStack - Core Service: Lambda
Episode 7 of 23

Learn LocalStack - Core Service: Lambda

Building serverless functions with Python, Node, Java, .NET 10, and Go runtimes, mastering create-function and invoke, understanding cold start and hot reload, then triggering Lambda from S3 and SQS events.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Introduction

In episode 6 we dissected DynamoDB as a datastore, and in episode 5 S3 as an object store. This time we get into the brain of serverless architecture: AWS Lambda. Lambda is the backbone of almost every event-driven pattern on AWS — from processing file uploads and reading message queues to serving API Gateway requests. That's why understanding how Lambda works in LocalStack is the most valuable skill in this series.

In this episode you'll build functions from scratch, run them across five different runtimes, dissect cold start behavior, and connect them to S3 and SQS triggers.

Building Your First Function with Python

Project Structure

Before deploying, you need handler code. Python Lambda expects a function with the signature (event, context) that returns a JSON-serializable value:

Pythonhandler.py
def handler(event, context):
    print("event diterima:", event)
    return {
        "statusCode": 200,
        "body": "Halo dari LocalStack Lambda!"
    }

Zip the handler folder first, then deploy:

Pack dan deploy fungsi Python
zip -r function.zip handler.py
awslocal lambda create-function \
  --function-name hello \
  --runtime python3.12 \
  --role arn:aws:iam::000000000000:role/lambda-role \
  --handler handler.handler \
  --zip-file fileb://function.zip

The role used is a fictitious ARN. LocalStack doesn't validate IAM as strictly as real AWS — the goal is for commands to look identical to production. The destination endpoint remains http://localhost:4566 because all commands are sent through awslocal, the wrapper of aws --endpoint-url.

Invoke

Invoke fungsi dan lihat hasilnya
awslocal lambda invoke --function-name hello \
  --payload '{"nama": "Arman"}' output.json
cat output.json

LocalStack genuinely executes the function inside a container, not just mocks it. To trace its internal process, run localstack logs while triggering an invoke.

Supporting Multiple Runtimes

One of LocalStack's strengths is runtime parity: you can deploy code for any language stack without downloading a special emulator:

RuntimeRuntime ValueNotes
Pythonpython3.12 / python3.13Fastest for prototyping
Node.jsnodejs22.xPopular runtime for APIs
Javajava21Requires building a JAR/zip
.NET 10dotnet10Newest runtime, supported since 2026.x
Goprovided.al2023Compile a binary, use a custom runtime

The command is exactly the same — just change the --runtime flag and handler. For Go, the handler points to the executable binary, not a function name:

Deploy fungsi Go
GOOS=linux GOARCH=amd64 go build -o bootstrap main.go
zip -r go-function.zip bootstrap
awslocal lambda create-function \
  --function-name go-fn --runtime provided.al2023 \
  --role arn:aws:iam::000000000000:role/lambda-role \
  --handler bootstrap --zip-file fileb://go-function.zip

Cold Start & Hot Reload

What Is a Cold Start

When a function is invoked after a long idle period, the runtime must be "started up" first: pull the image, initialize the runtime, then run the handler. This is called a cold start and adds latency of up to several seconds. Subsequent calls use a warm instance and are therefore much faster. In LocalStack, the first invoke is almost always cold — this teaches you to write code that doesn't assume state between invokes.

Hot Reload Without Redeploying

Redeploying with a ZIP every time you change a single line is slow. For a fast dev loop, LocalStack provides hot reload via volume mount. Run the container with the LAMBDA_MOUNT_CWD env var pointing to your code folder:

docker-compose.yml dengan hot reload
services:
  localstack:
    image: localstack/localstack:latest
    ports:
      - "4566:4566"
    environment:
      - LAMBDA_MOUNT_CWD=/code
    volumes:
      - ./functions:/code

Now edit functions/handler.py, and the function pointing to that folder will immediately use the latest code without update-function-code. This is the pattern teams use for fast iteration.

Warning

Hot reload only refreshes the mounted code. Changes to runtime, environment variables, or the handler still require awslocal lambda update-function-configuration.

Environment Variables & Layers

Environment Variables

Every function needs configuration like a table name or log level. Send it via --environment:

Set env vars saat deploy
awslocal lambda create-function \
  --function-name config-fn --runtime python3.12 \
  --role arn:aws:iam::000000000000:role/lambda-role \
  --handler handler.handler --zip-file fileb://function.zip \
  --environment 'Variables={ENV=dev,LOG_LEVEL=debug}'

Inside the handler, read them via os.environ.get("ENV") as usual — the syntax is identical to real AWS.

Layers for Sharing Dependencies

Layers share dependencies across functions (e.g. extra boto3 libraries or internal utilities). Publish a layer once, then attach it to many functions:

Publish dan pasang layer
zip -r layer.zip python/
awslocal lambda publish-layer-version \
  --layer-name deps --zip-file fileb://layer.zip
awslocal lambda create-function \
  --function-name with-layer --runtime python3.12 \
  --role arn:aws:iam::000000000000:role/lambda-role \
  --handler handler.handler --zip-file fileb://function.zip \
  --layers "arn:aws:lambda:us-east-1:000000000000:layer:deps:1"

Triggering Lambda from S3 & SQS

Lambda is rarely invoked manually; it's run by events. The two most common triggers are S3 (object created/deleted) and SQS (incoming message).

Trigger from S3

Pasang notifikasi S3 ke Lambda
awslocal s3 mb s3://uploads
awslocal lambda create-function \
  --function-name resize --runtime python3.12 \
  --role arn:aws:iam::000000000000:role/lambda-role \
  --handler handler.handler --zip-file fileb://function.zip
awslocal s3api put-bucket-notification-configuration \
  --bucket uploads --notification-configuration \
  '{"LambdaFunctionConfigurations":[{"LambdaFunctionArn":"arn:aws:lambda:us-east-1:000000000000:function:resize","Events":["s3:ObjectCreated:*"]}]}'
awslocal s3 cp image.jpg s3://uploads/image.jpg

Event Source Mapping from SQS

For SQS, use an event source mapping. LocalStack pulls messages from the queue and invokes the function automatically:

Hubungkan SQS ke Lambda
awslocal sqs create-queue --queue-name jobs
awslocal lambda create-event-source-mapping \
  --function-name worker \
  --event-source-arn arn:aws:sqs:us-east-1:000000000000:jobs
awslocal sqs send-message --queue-url http://localhost:4566/000000000000/jobs \
  --message-body '{"id": 42}'

Check the function logs with localstack logs — you'll see the incoming message payload as the event.

Tip

Be precise with the ARN in event source mapping: the format is arn:aws:sqs:<region>:<account>:<queue-name>. A wrong account or region means the mapping will never trigger an invoke.

Closing

Summary of this episode:

  • Deploy a function with awslocal lambda create-function and run it with awslocal lambda invoke; a fictitious IAM role is enough for emulation.
  • Five runtimes ready to use: Python, Node, Java, .NET 10, and Go via provided.al2023.
  • Cold start happens on the first invoke; use LAMBDA_MOUNT_CWD for hot reload.
  • Environment variables and layers work fully and are syntactically identical to production.
  • S3 and SQS triggers call functions automatically via notifications and event source mapping.

Lambda is now running in your emulator. But functions called one by one aren't very useful — event-driven architectures need a message pipeline. In episode 8 we dissect SQS and SNS: creating queues and topics, sending and receiving messages, dead-letter queues, visibility timeout, and fan-out patterns. See you there!

Learn LocalStack - Core Service: Lambda | Learn LocalStack