Automate AWS Lambda Layer Creation with Python and Shell Scripts
5 min read

Automate AWS Lambda Layer Creation with Python and Shell Scripts

Automate AWS Lambda Layer Creation with Python and Shell Scripts
Photo by Noah Negishi / Unsplash

In AWS Lambda, a layer is used to provide additional code and dependencies required by lambda functions to run successfully. A layer is actually a .zip archive file that helps abstracts away the dependent libraries needed to run a function from the business logic implemented in the function. The idea of a layer in AWS Lambda promotes code sharing and a best practice of separating business logic code design from dependencies required to execute the logic.

For various Python runtimes and versions, AWS Lambda has provided a bunch of libraries to support lambda functions execution. These libraries include all the Python builtin modules as well as other third-party libraries. A list of predefined Python modules available in AWS Lambda can be found here.

For the needed libraries that are not available in the predefined module list, users are expected to package and upload such libraries for the use of their lambda functions. The process of creating and packaging a lambda layer is pretty manual and can be tedious where more than one modules and of different versions are required to be packaged as a layer for a given lambda function. This post aims to automate the process by designing a python and a shell scripts that can be easily run with desired modules as arguments in order to create a .zip archive layer to be used with lambda functions.

Although layers make life easier when deploying lambda functions, there are important quotas that users need to be aware of. AWS currently allows a total of 5 layers per lambda function and the maximum size for an uploadable .zip archive deployment package (including layers and custom runtimes) is 50MB. The unzipped deployment package maximum size must not go above 250MB. It's therefore a good practice to ensure that too many libraries are not added to the layer to avoid ballooning the size of the package above the limit. It is also advisable to first go through the predefined module list to ensure that only modules that are not already included in AWS Lambda are being packaged as layers for lambda functions.

Solution Design Steps

The solution steps highlighted below assume that you have Anaconda or its smaller footprint version, Miniconda, installed.

Create a new conda environment with a required python version


# Define some variables
pyLayerEnv="layerEnv"
projectDir="projectLambdaLayer"
pythonVersion="3.12"
packageName="polars_s3fs_layer"

# Get your base conda environment name
condaBase=$(conda info | grep "base environment" | awk '{{ print "$4" }}' | awk -F"/" '{{ print "$NF" }}')

# Create a new conda environment with a new python version
conda create --name $pyLayerEnv python=$pythonVersion --yes

# configure conda to avoid CommandNotFoundError 
source ~/$condaBase/etc/profile.d/conda.sh

Activate the new conda environment


conda activate $pyLayerEnv

Create a project directory and navigate to the folder


mkdir $projectDir && cd $projectDir

Create a package path for libraries to be included in the layer


mkdir -p build/python/lib/python$pythonVersion/site-packages

Create a shell array and add the required libraries to it


declare -a libArr && libArr+=("polars" "s3fs")

Install each library in the array to the defined path


for item in "${{libArr[@]}}"
    do 
        pip install --platform manylinux2014_x86_64 "$item" -t build/python/lib/python$pythonVersion/site-packages
    done 

Consider removing unnecessary files and folders such as pycache, LICENSES, etc. to reduce the layer size.


find build/python/lib/python$pythonVersion/site-packages -type d -name "__pycache__" -exec rm -rf {{}} \;

find build/python/lib/python$pythonVersion/site-packages -type f -name "*LICENSE*" -exec rm -rf {{}} \;

Change to build directory and zip the installed libraries


cd build && zip -r $packageName python

Deactivate and remove the conda environment


conda deactivate && conda remove --name $pyLayerEnv --all --yes

Move the zip layer package into the project directory


mv $packageName.zip ../

If the zipped layer file is less than 10MB, you can upload it directly on the Lambda console when you're creating the layer. For a larger file size, you can upload it via Amazon S3.

Final Code Design

Putting the above shell commands together, you can run the code as either a standalone script in a shell or as a command within the subprocess python module as described below.


import argparse
import subprocess


def getParser():

    # create the parser
    parser = argparse.ArgumentParser(
        prog="createLambdaLayerPackage",
        description="Create a Lambda layer package",
        allow_abbrev=False,
    )

    # add arguments
    parser.add_argument(
        "--layer-package-name",
        required=True,
        type=str,
        help="Name of the package zip file",
    )

    parser.add_argument(
        "--runtime-version",
        required=True,
        type=str,
        help="Python version to use for the Lambda layer e.g. 3.12",
    )

    parser.add_argument(
        "--layer-library",
        nargs=argparse.REMAINDER,
        required=True,
        type=str,
        help="Python libraries to be included in the Lambda layer package. Separate multiple libraries with spaces.",
    )

    # parse the arguments
    args = parser.parse_args()
    return args



def createLambdaLayerPackage(myArgs: argparse.Namespace) -> None:
    """ A function to create an AWS Lambda layer package
    Args:
        --runtime-version (str): Python version to use for the Lambda layer e.g. 3.12
        --layer-library" (str): Python libraries to be included in the Lambda layer package.
        --layer-package-name (str): Name of the package zip file.

    Returns:
        A zip layer package/file under projectLambdaLayer directory.

    Usage: createLambdaLayerPackage.py --layer-package-name polars-pyarrow-layer --runtime-version 3.12 --layer-library polars pyarrow
    """

    try:
        # get a dict of arguments and values
        mydict = vars(myArgs)

        # extract values from arguments
        modList = mydict["layer_library"]
        runtimeVal = mydict["runtime_version"]
        packageVal = mydict["layer_package_name"]

        # combine layer-library arg values into a string
        modStr = " ".join(modList)

        shellCmd = f"""
            pyLayerEnv="envLayer"
            projectDir="projectLambdaLayer"
            pythonVersion={runtimeVal}
            packageName={packageVal}

            # get your base conda environment name
            condaBase=$(conda info | grep "base environment" | awk '{{ print "$4" }}' | awk -F"/" '{{ print "$NF" }}')

            # create a new conda environment with python version desired
            conda create --name $pyLayerEnv python=$pythonVersion --yes

            # configure conda to avoid CommandNotFoundError
            source ~/$condaBase/etc/profile.d/conda.sh

            # activate the conda environment
            conda activate $pyLayerEnv

            # create a project directory and navigate into the folder
            mkdir $projectDir && cd $projectDir

            # create a package path for libraries to be included in the layer
            mkdir -p build/python/lib/python$pythonVersion/site-packages

            # create an array to store libraries
            declare -a libArr

            # add libraries to array
            libArr+=({modStr})

            # install libraries in the array to the defined path
            for item in "${{libArr[@]}}"
                do
                    pip install --platform manylinux2014_x86_64 "$item" -t build/python/lib/python$pythonVersion/site-packages
                done

            # consider removing unnecessary files and folders such as __pycache__, LICENSES, etc. to reduce package size
            find build/python/lib/python$pythonVersion/site-packages -type d -name "__pycache__" -exec rm -rf {{}} \;
            find build/python/lib/python$pythonVersion/site-packages -type f -name "*LICENSE*" -exec rm -rf {{}} \;

            # change to build directory and zip the installed libraries
            cd build && zip -r $packageName python

            # deactivate and remove the conda environment
            conda deactivate && conda remove --name $pyLayerEnv --all --yes

            # move the zip layer package under the project directory
            mv $packageName.zip ../

            # step out of build folder
            cd ..

            # remove the build folder
            rm -rf build
            """

        # run the shell script
        result = subprocess.run(
            ["/bin/bash"],
            input=shellCmd,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            encoding="utf-8",
        )

        if result.returncode != 0:
            print(result.stderr)
            raise Exception("Error creating Lambda layer package")
        else:
            print("Lambda layer package created successfully")

    except Exception as err:
        print(err)


def main():
    # get the required arguments
    modArgs = getParser()

    # create the lambda layer
    createLambdaLayerPackage(modArgs)


if __name__ == "__main__":
    main()

Example Script Usage

The example below creates a layer consisting of polars and s3fs modules. When the script execution is completed, a .zip archive file polars-s3fs.zip will be created under the project folder named projectLambdaLayer in your current working directory.

python createLambdaLayerPackage.py --layer-package-name polars-s3fs --runtime-version 3.12 --layer-library polars s3fs 

Additionally, you can run the command below to see help tips on how to use the script and the required arguments.

python createLambdaLayerPackage.py -h

See the full code including the shell script in my Github repository. Thanks for reading.