Deploying AWS Lambda and S3 with CDK (Python)

This project walks through deploying a simple AWS Lambda function and S3 bucket using the AWS Cloud Development Kit (CDK) in Python. It’s designed for beginners exploring cloud infrastructure, DevOps, or Infrastructure as Code (IaC). Along the way, you'll learn Identity and Access Management (IAM) best practices, such as avoiding root credentials, and troubleshoot a real-world deployment issue using the command-line interface (CLI).

Skills Demonstrated

  • Infrastructure as Code using AWS CDK (Python)
  • Packaging and deploying AWS Lambda functions
  • Secure IAM practices (avoiding root credentials)
  • CloudFormation synthesis and asset management
  • Command-line troubleshooting and CDK internals

Prerequisites

  • AWS account (create one here)
  • AWS CLI v2 installed and configured
  • Python 3.8+ (tested with Python 3.12)
  • Node.js v14+ (tested with Node.js 22.16.0 and npm 10.9.2)
  • AWS CDK v2 installed: npm install -g aws-cdk

Setting Up AWS Access Without Root Credentials

As a new AWS user, configure your environment securely without using your root account. It's unsafe and should be avoided except for initial account setup.

Option 1: IAM User

  1. Go to the IAM Console.
  2. Create a new IAM user with:
    • Programmatic access
    • AdministratorAccess (for development only)
  3. Run the AWS CLI configuration:
aws configure --profile cdk-sre-demo

Enter:

  • Access key ID
  • Secret access key
  • Region (e.g., us-west-2)
  • Output format (optional: json)

Use this profile for all CDK commands:

cdk bootstrap --profile cdk-sre-demo

Option 2: IAM Identity Center (SSO)

  1. Run:
aws configure sso
  1. Authenticate:
aws sso login --profile my-sso-profile
  1. Use it with CDK:
cdk bootstrap --profile my-sso-profile

See: AWS userguide to SSO CLI Configuration

Quick Setup Recap

  • Never use root credentials for CLI/CDK access.
  • Use an IAM user or SSO to configure access:
    • aws configure --profile or aws configure sso
  • Always pass the correct profile to CDK with --profile

Project Setup

Initialize the CDK Project


      mkdir cdk-sre-demo && cd cdk-sre-demo
      cdk init app --language python
      

Note: The folder must be empty before running cdk init.

After initialization, your folder should look like this:


      cdk-sre-demo/
      ├── app.py
      ├── cdk_sre_demo/
      │   ├── __init__.py
      │   └── cdk_sre_demo_stack.py
      ├── requirements.txt
      ├── lambda/
      │   └── hello_lambda.py
      ├── .gitignore
      ├── README.md
      └── cdk.json
      

Create Virtual Environment and Install Dependencies


      python -m venv .venv
      source .venv/bin/activate
      pip install -r requirements.txt
      

Confirm requirements.txt contains:


      aws-cdk-lib==2.139.0
      constructs>=10.0.0,<11.0.0
      

Write Your Lambda Function

Create lambda/hello_lambda.py with the following content:


      def handler(event, context):
          print("Hello from Lambda!")
          return {
              'statusCode': 200,
              'body': 'Lambda function executed successfully.'
          }
      

Define Your Infrastructure Stack

In cdk_sre_demo/cdk_sre_demo_stack.py, define the infrastructure:


      from aws_cdk import (
          Stack,
          aws_s3 as s3,
          aws_lambda as _lambda,
      )
      from constructs import Construct
      
      class CdkSreDemoStack(Stack):
          def __init__(self, scope: Construct, construct_id: str, **kwargs):
              super().__init__(scope, construct_id, **kwargs)
      
              s3.Bucket(self, "SreDemoBucket", versioned=True)
      
              _lambda.Function(
                  self, "HelloLambda",
                  runtime=_lambda.Runtime.PYTHON_3_10,
                  handler="hello_lambda.handler",
                  code=_lambda.Code.from_asset("lambda")
              )
      

Verify Entry Point (app.py)

Make sure app.py contains:


      #!/usr/bin/env python3
      import aws_cdk as cdk
      from cdk_sre_demo.cdk_sre_demo_stack import CdkSreDemoStack
      
      app = cdk.App()
      CdkSreDemoStack(app, "CdkSreDemoStack")
      app.synth()
      

Deploy the Stack


      cdk bootstrap --profile cdk-sre-demo
      cdk deploy --profile cdk-sre-demo
      

Troubleshooting Deployment: Lambda Not Zipping Correctly

Error Description

Sometimes (or most of the time) things don’t go as smoothly as expected. In my case, every time I ran cdk deploy, I hit an error that indicated:

Uploaded file must be a non-empty zip (Service: Lambda, Status Code: 400…)

This hinted that CDK wasn’t zipping and staging my Lambda function properly. More accurately, CDK failed to detect the Lambda directory as a valid asset. As a result, it didn't bundle and upload the function code to S3, causing a Lambda deployment error.

I double-checked my functions and folder structure, but everything looked correct. To investigate further, I ran:

cdk synth --profile dev-user

This command synthesizes your CDK app into a CloudFormation template and outputs the resource definitions. Normally, under the Lambda section, you should see a ZIP file reference like this:


Resources:
  HelloLambda…:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        S3Bucket: cdk-hnb659fds-assets-<AccountId>-<Region>
        S3Key: 12118b59e….zip
  

Which I did, but when I ran:

find cdk.out -type f -name '*.zip'

No .zip file was found! That’s when I realized: CDK wasn’t packaging the function directory at all. It was just copying the hello_lambda.py file directly into cdk.out/assets/.

Workaround: Manually Zip the Lambda Function

To keep the project moving, I used a simple workaround: manually zipped the Lambda file and referenced it explicitly in the CDK stack.


cd lambda
zip -j ../hello_lambda.zip hello_lambda.py
cd ..
  

This will result in the .zip file in your project root directory.

Then in cdk_sre_demo_stack.py, I updated the Lambda block:


_lambda.Function(
    self, "HelloLambda",
    runtime=_lambda.Runtime.PYTHON_3_12,
    handler="hello_lambda.handler",
    code=_lambda.Code.from_asset("hello_lambda.zip")
)
  

Code.from_asset(path) tells CDK to package a local file or folder and upload it to S3 as a Lambda asset.

Clean and Re-Synthesize

To clean your directory of old deployment files, remove previously synthesized outputs before re-synthesizing:


rm -rf cdk.out
cdk synth --profile cdk-sre-demo
  

Double-check that the zipped folder is in the cdk.out directory by running:

find cdk.out -type f -name '*.zip'

Unzip:

unzip -l cdk.out/assets/<that-hash>.zip

If it lists hello_lambda.py, you're good to go!

Final Deployment


cdk bootstrap --profile dev-user
cdk deploy --profile dev-user
  

Verifying Resources

You can confirm your resources are up and running in two ways, AWS UI or via terminal:

Option 1: AWS Console

S3 Bucket

  • Go to the S3 Console.
  • Look for a bucket named something like CdkSreDemoStack-SreDemoBucketXXXXX.
  • Click into it to confirm it exists and versioning is enabled.

Lambda Function

  • Go to the Lambda Console.
  • Find the function named CdkSreDemoStack-HelloLambdaXXXXX.
  • Click it, and then:
    • In the Monitor tab → check the Log streams to ensure the function executed successfully without errors.
    • In the Test tab, create a test event, run the function, and verify the output.
    • You should see: “Lambda function executed successfully.”

Option 2: AWS CLI

Verify the S3 bucket

If you prefer to confirm your resources through the terminal, run the below command:

aws s3 ls --profile cdk-sre-demo

Check if versioning is enabled:


aws s3api get-bucket-versioning \
  --bucket your-bucket-name-here \
  --profile cdk-sre-demo
  

You should see something like:


{
  "Status": "Enabled"
}
  

This confirms the bucket was created with versioning, as specified in your CDK stack.

Verify the Lambda function

List Lambda Functions:

aws lambda list-functions --profile dev-user

Invoke the Function:


aws lambda invoke \
  --function-name <your-function-name> \
  --profile dev-user \
  response.json

cat response.json
  

You should see something like:


{
  "statusCode": 200,
  "body": "Lambda function executed successfully."
}
  

Note: You can also see this function invocation in the AWS Lambda console.

Tearing Down

To delete everything you deployed:

cdk destroy --profile dev-user