CDK can bundle Python Lambda functions using Docker, but it often feels like magic because the underlying mechanism isn’t immediately obvious.
Let’s see this in action. Imagine you have a simple Python Lambda function app.py:
import json
import os
def lambda_handler(event, context):
message = os.environ.get('GREETING', 'Hello')
name = event.get('name', 'World')
return {
"statusCode": 200,
"body": json.dumps({
"message": f"{message}, {name}!"
})
}
And you want to deploy it using AWS CDK. Here’s how you’d define it in your CDK stack (TypeScript):
import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as path from 'path';
export class MyLambdaStack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const myLambda = new lambda.Function(this, 'MyPythonLambda', {
runtime: lambda.Runtime.PYTHON_3_9,
handler: 'app.lambda_handler',
code: lambda.Code.fromAsset(path.join(__dirname, '../lambda')), // Points to the directory containing app.py
environment: {
GREETING: 'Greetings'
}
});
}
}
When you run cdk deploy, CDK does more than just upload your app.py. It actually builds a Docker image for your Lambda function. This image contains your Python code, the specified runtime, and any dependencies you might have. CDK then uploads this Docker image to Amazon Elastic Container Registry (ECR) and configures your Lambda function to use that image.
The problem CDK solves here is managing the execution environment for your Lambda functions, especially when they have external dependencies or require specific system-level packages. Instead of relying on the pre-built Lambda runtimes, you’re essentially bringing your own, packaged as a Docker image.
Internally, when lambda.Code.fromAsset() is used with a directory containing a requirements.txt or Pipfile, CDK invokes a build process. By default, for Python, it uses a Docker image provided by AWS that mimics the Lambda execution environment. This builder image fetches your requirements.txt, installs them into a virtual environment within a temporary directory, and then packages this entire environment, along with your Lambda code, into a new Docker image. This image is then pushed to ECR and referenced by the Lambda function.
The exact levers you control are primarily through the lambda.Function construct. The runtime parameter still dictates the base operating system and Python version within the Docker image. The code parameter, using lambda.Code.fromAsset(), is where you point CDK to your function’s source code directory. If this directory contains a requirements.txt file, CDK automatically infers that dependency installation is needed. You can also specify custom build arguments or commands if you need more advanced control over the Docker build process, though this is less common for standard Python Lambdas.
What most people don’t realize is that CDK is not just zipping your code. It’s performing a full Docker build, creating an OCI-compliant image that’s then uploaded to ECR. This means that if your requirements.txt includes packages that need compiling (like numpy or pandas with C extensions), CDK’s builder will attempt to compile them within the Docker build environment, ensuring compatibility with the Lambda runtime’s underlying OS. This is a significant advantage over simply zipping your code, which would fail if the compiled extensions weren’t built for the same architecture as the Lambda environment.
The next step is understanding how to manage layers for your Python Lambda functions when using custom Docker builds.