Building a Serverless Flask Application in Python: Deploying a Docker Image on AWS Lambda with API Gateway
Our goal
This article continues from my previous piece, “A Step-by-step guide to running a Python Docker Image on AWS Lambda”, In this article, we’ll deploy a Flask application as a Docker image within a Lambda function, making it accessible through an API Gateway. This architecture is particularly useful for delivering a robust backend for applications.
Want to get notified about my future posts? Subscribe here
First things first
To follow this article you’ll need:
- Docker installed and configured
- AWS CLI installed and configured
- AWS CDK installed and configured
- Basic knowledge of Python and Flask
- Be able to create an API in AWS API Gateway
Once pre-requirements have been checked, let's dive in.
Let’s code
Let’s begin by creating a folder for our project:
mkdir flask-python-lambda
In the folder you created, type the following command.
cdk init app --language typescript
The command “cdk init app — language typescript” initializes a new AWS CDK application using TypeScript as the programming language. Here’s a breakdown of its purpose:
- cdk init: This is the command to create a new CDK project.
- app: Specifies that you want to create a new CDK application.
- language typescript: Indicates that the application should be set up using TypeScript, allowing you to write your infrastructure code in this specific language. We’ll explore more details about this later in the article.
All in all, this command sets up the necessary files and directory structure for a TypeScript-based AWS CDK project, enabling you to define and manage your cloud resources programmatically.
As mentioned before, this article continues my previous piece, so I won’t cover all the configuration steps here. For a complete guide, please refer to my earlier article.
Dependencies
It’s always a good practice to create a virtual environment for installing the project dependencies. If you’d like to create one, please do so within image/src folder.
We will need two dependencies in this article:
Flask and aws-wsgi
I believe you know what Flask is so I won’t dive into this dependency, However, it’s worth explaining aws-wsgi: A wrapper that helps deploy WSGI-compatible applications (like those built with Flask) on AWS services such as Lambda, facilitating serverless deployment.
pip install Flask aws-wsgi
Once the dependencies are installed, we need to create a requirements.txt
file. This file will be used during the Docker image build process for Lambda deployment. To create the file, run the command below in the image folder.
pip freeze > requirements.txt
Dockerfile
Our Dockerfile will look like this:
FROM public.ecr.aws/lambda/python:3.11
# 2 - Copy requirements.txt
COPY requirements.txt ${LAMBDA_TASK_ROOT}
# 3 - Install all the packages
RUN pip install -r requirements.txt
# 4 - Copy all files in ./src to the LAMBDA_TASK_ROOT
COPY src/ ${LAMBDA_TASK_ROOT}
# 5 - Set the CMD to the handler.
CMD [ "main.handler" ]
Just out of curiosity, I’ve explained in a previous article why we need to use ${LAMBDA_TASK_ROOT}
.
Checking the progress
After all configuration’s been done, the project will have this structure:
flask-python-lambda/
├── README.md
├── bin
│ └── flask-python-lambda.ts
├── cdk.json
├── image
│ ├── Dockerfile
| ├── requirements.txt
│ └── src
│ └── main.py
├── jest.config.js
├── lib
│ └── flask-python-lambda-stack.ts
├── node_modules
├── package-lock.json
├── package.json
├── test
│ └── flask-python-lambda.test.ts
└── tsconfig.json
To develop our Lambda function, we’ll need to create a file named main.py
. The code will look like this:
import awsgi
from flask import (Flask)
from webhooks import webhook
def create_app():
app = Flask(__name__)
# Import and register blueprints, if any
app.register_blueprint(webhook)
return app
def handler(event, context):
print("Flask app started")
app = create_app()
return awsgi.response(app, event, context)
To organize our code, we’ll create another file namedwebhook.py
. This file declares all the routes we want to have.
from flask import (
Blueprint,
jsonify
)
webhook = Blueprint("webhooks", __name__)
@webhook.route("/welcome", methods=["GET"])
def webhook_welcome():
print('testing log from welcome function')
return jsonify(status=200, message='Welcome to the webhook!')
@webhook.route("/", methods=["GET"])
def webhook_root():
print('testing log from root function')
return jsonify(status=200, message='OK')
Deploy
It’s time to deploy our docker image. To do this, use the command below:
cdk deploy
The command will produce a result like this:

Tests
The easiest way to test the Lambda function is by using the test functionality provided by the AWS Console. You can create a new test by choosing the api-gateway-aws-proxy.

Change the path
and httpMethod
variables to test the desired route. In this example, the route to be tested is /welcome
, and the httpMethod
is GET
.
The test result will look like this:

API Gateway
Create a REST API and add a resource called welcome
, which matches the route we used.
The resource will be set up as a proxy integration, sending requests directly to the Lambda function. Be sure to select ANY
as the method type and enable Lambda proxy integration toggle.

Let’s deploy the API and test it using the URL provided by API Gateway. The result will look like this:

Well, we did it!
Pros and cons
You may be wondering why you need to do all this work and whether this architecture is effective. That’s a valid question, and I had similar thoughts when I first encountered it. To help clarify, I’ll list some pros and cons of this architecture.
Pros
- Cost: This architecture is more cost-effective compared to an architecture that uses EKS/ECS, ECR, Load Balancer, and VPC.
- Reusable: This architecture is based on Docker Image, so you can deploy the same image in an ECS or EKS cluster without changing your code.
Cons
- Performance: Due to Lambda’s cold start, this architecture may take a bit of time to respond to its initial requests.
- API Gateway Proxy integration: All response transformations must be configured directly in your code.