SAM and Docker can be a powerful combination for local Lambda testing, but getting them to play nice can feel like a dark art.

Here’s a look at SAM’s local testing workflow and how Docker fits in:

The Core Workflow: SAM CLI local invoke

The primary tool for local Lambda testing with SAM is the sam local invoke command. This command takes your Lambda function code and runs it within a Docker container that mimics the AWS Lambda execution environment.

Let’s say you have a simple Python Lambda function app.py:

import json

def lambda_handler(event, context):
    return {
        "statusCode": 200,
        "body": json.dumps({
            "message": "hello world",
            "input_event": event
        })
    }

And your template.yaml looks like this:

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: A simple Lambda function

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: my-hello-world-function
      Handler: app.lambda_handler
      Runtime: python3.9
      MemorySize: 128
      Timeout: 30
      Policies: AWSLambdaBasicExecutionRole

To invoke this function locally, you’d use:

sam local invoke HelloWorldFunction --event events/event.json

Where events/event.json contains your test event payload:

{
  "key1": "value1",
  "key2": "value2",
  "key3": "value3"
}

SAM CLI will then:

  1. Read your template.yaml to understand the function’s configuration (runtime, handler, etc.).
  2. Build your Lambda function’s deployment package if it’s not already built (e.g., if you have requirements.txt for Python).
  3. Pull the appropriate Docker image for the specified runtime (e.g., public.ecr.aws/lambda/python:3.9).
  4. Start a Docker container running that image.
  5. Mount your function’s code into the container.
  6. Execute your handler function within the container, passing the event and a mock context object.
  7. Return the function’s output.

The sam local start-api Workflow

For testing API Gateway integrations, sam local start-api is your go-to. It spins up a local web server that emulates API Gateway, routing requests to your Lambda functions.

Using the same template.yaml and app.py as above, if you add an API Gateway event source:

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: A simple Lambda function

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: my-hello-world-function
      Handler: app.lambda_handler
      Runtime: python3.9
      MemorySize: 128
      Timeout: 30
      Policies: AWSLambdaBasicExecutionRole
      Events:
        HelloWorldApi:
          Type: Api
          Properties:
            Path: /hello
            Method: get

You can start the local API endpoint with:

sam local start-api

This command will:

  1. Build your function code.
  2. Pull the necessary Docker images.
  3. Start a Docker container for each Lambda function defined in your template.
  4. Set up a local web server (typically on http://127.0.0.1:3000).
  5. When you send a GET request to http://127.0.0.1:3000/hello, SAM CLI will intercept it, transform it into a Lambda event payload, and invoke the HelloWorldFunction in a Docker container.
  6. The response from your Lambda function is then translated back into an HTTP response and sent back to your client.

Debugging with Docker

SAM CLI integrates with Docker for debugging. You can use the --debug flag with sam local invoke or sam local start-api to enable remote debugging.

For Python, this typically involves:

  1. Modifying your handler: You might need to add a small snippet to your Lambda code to start a debug server.
  2. Running sam local invoke with debug flags:
    sam local invoke HelloWorldFunction --event events/event.json --debug-port 5678 --debugger python
    
  3. Attaching your IDE’s debugger: Configure your IDE (e.g., VS Code, PyCharm) to connect to localhost:5678.

When the Lambda function starts in the Docker container, it will pause execution until your IDE attaches to the debug port. This allows you to step through your code, inspect variables, and set breakpoints just as you would in a local application.

Configuration and Customization

SAM CLI provides several ways to customize the Docker environment:

  • --docker-network: Connect your Lambda containers to a specific Docker network. This is useful if your Lambda function needs to communicate with other services running in Docker containers on the same network. For example:
    sam local invoke HelloWorldFunction --event events/event.json --docker-network my-custom-network
    
  • --docker-volume: Mount additional volumes into the Lambda container. This can be used for configuration files, local data stores, or custom binaries.
    sam local invoke HelloWorldFunction --event events/event.json --docker-volume $(pwd)/config:/opt/config
    
  • --build-dir: Specify a directory for SAM to perform build operations.
  • --skip-pull-image: Avoid pulling Docker images if you already have them locally.

The Underlying Docker Images

SAM CLI uses specific Docker images that are designed to replicate the AWS Lambda runtime environment. These images are typically hosted on Amazon ECR Public. For example, a Python 3.9 function will use an image similar to public.ecr.aws/lambda/python:3.9.

When SAM CLI needs to run a function, it checks if the required image is present locally. If not, it pulls it from the ECR Public registry. This ensures that your local testing environment closely matches the AWS Lambda service.

The way SAM CLI injects your code and dependencies into these base images is a key part of its magic. It creates a temporary layer on top of the base image that includes your compiled code, node_modules, requirements.txt dependencies, etc., before starting the container.

The most common pitfall is when your local Docker daemon isn’t running or accessible, or when SAM CLI can’t find or pull the necessary Docker image. If you see errors related to "Docker daemon is not running" or "image not found," double-check your Docker setup.

Want structured learning?

Take the full Lambda course →