Right, so you’ve written a nifty little Python function. It works on your machine. Of course it does. The real trick is getting it to run on someone else’s computer—specifically, Amazon’s sprawling, globe-spanning network of servers, without you having to rent a single one of them. That’s the promise of AWS Lambda, and it’s a good one. But the path from a neat my_cool_function.py on your laptop to a deployed, running Lambda is paved with a few gotchas. Let’s navigate them together.

The core concept you have to wrap your head around is this: Lambda doesn’t install your dependencies with pip. It expects you to hand it a complete package—a zip file containing your function code and every single library it needs, all the way down to the binary dependencies. If it’s not in that zip, it doesn’t exist.

The Anatomy of a Lambda Deployment Package

Forget your fancy virtual environments for a second; we’re dealing with something more fundamental. Your deployment package is a flat directory that gets zipped. The critical rule: your external dependencies must be placed in a subdirectory named python/. Why? Because when AWS unzips your package onto its Lambda execution environment, it adds this python/ directory to the Python path. It’s their convention, not ours. We just have to play along.

Here’s how you build it correctly, from the shell up. This isn’t the only way, but it’s the most transparent and helps you understand what’s actually happening.

# Create a clean directory for our build
mkdir lambda-package
cd lambda-package

# Install ALL your dependencies into the 'python' subdir
# This is the magic. The '--target' flag is your best friend.
pip install requests pandas --target ./python

# Now, copy your actual function code into the root of the package
cp ../my_lambda_function.py .

# Zip it all up, making sure to include the contents of the 'python' dir
zip -r ../my-deployment-package.zip .

Your resulting zip file’s structure should look like this:

my-deployment-package.zip
├── my_lambda_function.py
└── python/
    ├── requests/
    ├── pandas/
    ├── numpy/
    └── ...etc

The Handler: Your Function’s Front Door

Your Lambda function needs an entry point, a “hey, over here!” signal. This is the handler. It’s not just any function; it’s a specific function you tell Lambda to invoke. It takes two arguments: the event (the data that triggered the function) and the context (runtime information from Lambda).

# my_lambda_function.py
import requests
from pandas import DataFrame

def lambda_handler(event, context):
    """
    The official handler AWS Lambda will call.
    event: Usually a dict, contains payload from the trigger (e.g., an API Gateway request)
    context: Contains methods and properties about the invocation and runtime (e.g., remaining time)
    """
    url = event.get('url')
    if not url:
        return {"statusCode": 400, "body": "Missing 'url' in event"}

    try:
        response = requests.get(url)
        response.raise_for_status()  # Raises an exception for bad HTTP status codes

        # Do something silly with pandas to prove we can
        df = DataFrame({'status_code': [response.status_code], 'text': [response.text[:10]]})
        print(f"Here's a dataframe, because why not:\n{df}")

        return {
            "statusCode": 200,
            "body": f"Successfully fetched {url}. Status: {response.status_code}"
        }
    except requests.exceptions.RequestException as e:
        return {"statusCode": 500, "body": f"An error occurred: {str(e)}"}

You then tell AWS Lambda the file name and the function name, concatenated with a dot: my_lambda_function.lambda_handler.

The Dependency Nightmare (and How to Avoid It)

Here’s where they get you. If your dependencies include packages with C extensions (like pandas, numpy, cryptography, etc.), you can’t just pip install them on your MacBook and zip them up. The compiled binaries are specific to your operating system. A Lambda runs on Amazon Linux. If you give it a Mac binary, it will stare at you blankly and throw a cryptic import error.

The solution? Build your package on a machine that matches the Lambda execution environment. The easiest way is to use a Docker container that mimics Amazon Linux.

# Use the AWS-provided Docker image that matches the Lambda runtime
docker run -v $(pwd):/outputs -it amazon/aws-lambda-python:3.9 bash

# Now you're inside a container that looks just like a Lambda environment
mkdir /tmp/build
cd /tmp/build
pip install requests pandas --target ./python
cp /outputs/my_lambda_function.py .
zip -r /outputs/my-deployment-package.zip .
exit

Now the my-deployment-package.zip file on your host machine is built with the correct Linux binaries. This is non-negotiable for anything beyond pure Python libraries.

Deployment: ZIP Upload vs. Beyond

The simplest way to deploy is to just upload that ZIP file directly through the AWS Console. It works. But you’re reading this book, so you’re not about to do that manually every time. This is where CI/CD and Infrastructure-as-Code (IaC) tools like Terraform or the AWS Cloud Development Kit (CDK) come in. They let you define your function and its configuration as code.

Here’s a bare-bones example using the AWS CLI to deploy your zip file, which you could script in a CI pipeline:

aws lambda update-function-code \
    --function-name MyPythonFunction \
    --zip-file fileb://./my-deployment-package.zip

The real pros use Lambda Layers for common dependencies and tools like the AWS SAM CLI or CDK which can abstract away the packaging process, but understanding what’s happening underneath the hood is what will save you when those tools inevitably do something inexplicable and you need to debug it. You now know what that zip file should look like. Never let it surprise you again.