Serving Dynamic Web Pages using Python and AWS Lambda

While AWS Lambda functions are typically used to build API endpoints, at their core Lambda functions can return almost anything. This includes returning html markup with dynamic content.

AWS Lambda + Python + Jinja

I will not go into details describing how to deploy AWS Lambda functions. Please see the official documentation. I will however describe how to return dynamic html content instead of a typical JSON.

If you prefer to develop and test lambda functions locally (as I do), you can use Docker to simulate the AWS lambda function environment. A sample Dockerfile I use is below.

FROM amazonlinux:latest
RUN mkdir -p /mnt/app
ADD . /mnt/app
WORKDIR /mnt/app
RUN yum update -y
RUN yum install gcc -y
RUN yum install gcc-c++ -y
RUN yum install findutils -y
RUN yum install zip -y
RUN amazon-linux-extras install python3=3.6.2
RUN pip3 install --upgrade pip
RUN pip3 install -r requirements.txt -t aws_layer/python

The requirements.txt includes just one package for simplicity. It is the common templating for Python called Jinja2


You can test your Lambda function by simple calling it with sample parameters:

import lambda_function
event = {
"queryStringParameters": {
"param1": "value1"
"path": "/api",
"requestContext": {
"param2": "value2"
res = lambda_function.lambda_handler(event=event, context={})
assert 200 == int(res["statusCode"])

In this step, we write the html template the Lambda function will return. A good default is the new Bootstrap 5 CSS framework where the recommended starting markup looks something like this:

Sample HTML page

Saving this file in folder “templates” and naming it index.html, we are ready to write the Lambda function.

In the example below, the lambda function expects URL parameters and parses those. So when parsing a custom URL, the format would look something like this: See step 10 in this tutorial to add custom URLs to your API Gateway-triggered Lambda functions.

import os
import sys
from jinja2 import Environment, FileSystemLoader
def lambda_handler(event, context):
env = Environment(loader=FileSystemLoader(os.path.join(os.path.dirname(__file__), "templates"), encoding="utf8"))
my_name = False
if event["queryStringParameters"] and "my_name" in event["queryStringParameters"]:
my_name_query = event["queryStringParameters"]["my_name"]
template = env.get_template("index.html")
html = template.render(
return response(html)
def response(myhtml):
return {
"statusCode": 200,
"body": myhtml,
"headers": {
"Content-Type": "text/html",
  • jinja2 loads your previously created index.html using class “FileSystemLoader” and we store it as variable “env”
  • variable “my_name” is parsed from the URL query parameters as explained above and stored as the Python variable my_name_query
  • the jinja2 render function then passes my_name_query to the template and returns the html page

Also published on

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store