AWS Builder Center

AWS Lambda Layers in Practice with Python: Sharing Dependencies Across Functions

Learn how to create an AWS Lambda Layer with Python dependencies and reusable utility functions, then share it across two Lambda functions. This hands-on lab walks you through packaging, publishing, testing, and cleaning up a Layer using the AWS CLI

Cloud Enginner
When we start building multiple AWS Lambda functions, we often notice that some code gets repeated. One function may need an external library, another may use the same dependency, and both may share validators, response formatters, or logging utilities. One option is to include everything in each function's deployment package. This works, but it creates unnecessary duplication.
 
In this lab, we will take a different approach: put shared components into an AWS Lambda Layer and let each function remain responsible for its own code.
 
Let's get started!

What Is a Lambda Layer?

An AWS Lambda Layer is a package that contains libraries, dependencies, or other components that can be reused by one or more Lambda functions. Instead of including these files in every function package, we can provide them in a separate layer. When a function uses a Layer, Lambda makes its contents available at runtime, allowing the code to import the modules and libraries included in it.
 
In practice, this helps you:
  • Avoid duplication: share dependencies and common code across multiple functions.
  • Simplify maintenance: update shared components without manually bundling them into each function's code.
  • Organize the project: keep business logic separate from dependencies and reusable utilities.
  • Reduce individual package sizes: remove components that are already available in the Layer.
 
A Layer has its own versions, each identified by a specific ARN. This means different functions can use different versions of the same Layer. Keep in mind that Layers do not replace dependency management or eliminate the need for compatibility testing. They are a way to organize and share components, especially when multiple functions need the same libraries.

What We Will Build

In this lab, we will create two Python Lambda functions that use the same Layer, which contains an external dependency and a module with helper functions.
The functions will be:
  • process_order: simulates order processing by validating required fields and checking stock availability.
  • validate_customer: checks a customer's required information and returns details about their record.
The Layer will provide the following components:
  • requests: a Python library used as an external dependency.
  • shared_utils.py: a module with reusable functions for standardizing responses, validating data, and logging events.

The Problem

Imagine two functions with the following structure:
1
2
3
4
5
6
7
8
9
process-order.zip
├── process_order.py
├── requests/
└── shared_utils.py

validate-customer.zip
├── validate_customer.py
├── requests/
└── shared_utils.py
 
The application works, but we are distributing the same dependency and shared code twice. The idea is to follow the DRY principle (Don't Repeat Yourself).
With a Layer, our architecture looks like this:
1
2
3
4
5
6
7
8
9
10
Lambda Layer
┌─────────────────────┐
│ requests │
│ shared_utils.py │
└──────────┬──────────┘
│
┌────────┴────────┐
│ │
▼ ▼
process_order validate_customer
 
This keeps the function packages smaller, with each function containing only what is specific to its responsibility. Layers also help separate function logic from its dependencies and share components across multiple functions.

Expected Result

The final setup will look like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
AWS Lambda
│
Shared Layer
│
┌───────────┴───────────┐
│ │
▼ ▼
process-order validate-customer
│ │
└───────────┬───────────┘
│
shared_utils.py
+
requests
 
We will publish the Layer as its own version, and both functions will use that version.

Prerequisites

  • AWS CLI configured with a profile that has the permissions needed to create the resources.
  • Python 3.
  • pip.
  • zip and unzip.
  • An AWS Region for the lab. By default, we will use the infamous and beloved us-east-1.

Step-by-Step Guide

1. Clone the Repository and Configure the Profile

Open the lab's GitHub repository, lab-lambda-layers , then clone it and enter the project directory to get the Python files and requirements.txt:
1
2
git clone https://github.com/ludsilva/lab-lambda-layers.git
cd lab-lambda-layers
For this lab, I will use Linux. We can set the AWS profile and Region for the current terminal session:
1
2
export AWS_PROFILE=teste_lab
export AWS_REGION=us-east-1
Note: Replace teste_lab with the name of the profile you use in AWS.
 
Confirm your identity:
1
aws sts get-caller-identity
 
Run this check before creating resources, especially if you have multiple profiles configured. It confirms that you are using the correct account and credentials—no one wants to discover they ran the lab in the wrong account.

2. Prepare the Layer

For a Python Layer, the ZIP file must have a python directory at its root. One supported path for libraries is python/lib/python3.x/site-packages. Let's create that structure:
1
mkdir -p lambda-layer/python/lib/python3.12/site-packages
Now install the dependencies directly into this structure using the project's requirements.txt:
1
2
pip install -r requirements.txt \
-t lambda-layer/python/lib/python3.12/site-packages/
The requirements.txt file pins the version used in this example:
1
requests==2.32.5
 
Pinning the version makes the build more predictable. In real projects, keep this file updated in a controlled way and test changes before publishing a new Layer version.

3. Add Your Own Code to the Layer

A Layer does not have to contain only third-party libraries; it can also provide your own Python modules. In this example, the module is named:
1
shared_utils.py
Copy it to:
1
2
cp shared_utils.py \
lambda-layer/python/lib/python3.12/site-packages/
The directory structure should look like this:
1
2
3
4
5
6
7
8
9
10
lambda-layer/
└── python/
└── lib/
└── python3.12/
└── site-packages/
├── requests/
├── urllib3/
├── certifi/
├── ...
└── shared_utils.py
 
shared_utils.py provides four reusable functions:
  • standard_response(): standardizes the response.
  • validate_request(): checks required fields.
  • log_event(): writes structured JSON logs.
  • requests_version(): returns the version of the requests library to confirm that the dependency is accessible through the Layer.

4. Package the Layer

Change to the directory:
1
cd lambda-layer
Zip the python directory so that it remains at the root of the ZIP archive:
1
zip -r ../shared-python-components.zip python
Return to the parent directory and inspect the contents:
1
2
cd ..
unzip -l shared-python-components.zip
 
The ZIP must contain python/ at the root, not an outer directory such as lambda-layer/python/.

5. Publish the Layer

Get the account ID:
1
2
3
ACCOUNT_ID=$(aws sts get-caller-identity \
--query Account \
--output text)
Publish the Layer:
1
2
3
4
5
6
7
aws lambda publish-layer-version \
--layer-name shared-python-components \
--region "$AWS_REGION" \
--description "Shared Python dependencies and utilities" \
--zip-file fileb://shared-python-components.zip \
--compatible-runtimes python3.12 \
--compatible-architectures x86_64
Save the ARN returned by the command. It identifies a specific version of the Layer. The ARN will have a format similar to this:
1
arn:aws:lambda:${AWS_REGION}:${ACCOUNT_ID}:layer:shared-python-components:1
Store the ARN in a variable, replacing the example version number with the one returned by the publication command:
1
LAYER_ARN="arn:aws:lambda:${AWS_REGION}:${ACCOUNT_ID}:layer:shared-python-components:1"
Note: 1 is only an example. Check the actual ARN returned when you publish the Layer.

6. Create the IAM Role

Lambda functions need an IAM role that allows the service to assume the role and write logs to CloudWatch. If you already have a suitable role, you can reuse it.
 
If you need to create a role for this lab, create the trust policy file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
cat > trust-policy.json <<'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
Create the role:
1
2
3
aws iam create-role \
--role-name lambda-layers-lab-role \
--assume-role-policy-document file://trust-policy.json
Attach the basic execution policy:
1
2
3
aws iam attach-role-policy \
--role-name lambda-layers-lab-role \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Get the ARN:
1
2
3
4
ROLE_ARN=$(aws iam get-role \
--role-name lambda-layers-lab-role \
--query 'Role.Arn' \
--output text)

7. Package and Create the process-order Function

The function package should contain only the handler code. Do not include requests or shared_utils.py, because these components will be provided by the Layer.
1
zip process-order.zip process_order.py
Create the function:
1
2
3
4
5
6
7
aws lambda create-function \
--function-name process-order \
--runtime python3.12 \
--handler process_order.lambda_handler \
--zip-file fileb://process-order.zip \
--role "$ROLE_ARN" \
--layers "$LAYER_ARN"
 
The Layer ARN must point to a specific version.

8. Package and Create the validate-customer Function

Repeat the process for the second function:
1
zip validate-customer.zip validate_customer.py
Create the function:
1
2
3
4
5
6
7
aws lambda create-function \
--function-name validate-customer \
--runtime python3.12 \
--handler validate_customer.lambda_handler \
--zip-file fileb://validate-customer.zip \
--role "$ROLE_ARN" \
--layers "$LAYER_ARN"
Both functions are now associated with the same Layer version.

9. Test the Order Function

Create an order event:
1
2
3
4
5
6
7
cat > order-event.json <<'EOF'
{
"product_id": "SKU-8472",
"quantity": 4,
"customer_id": "CUS-208"
}
EOF
Invoke the function:
1
2
3
4
5
aws lambda invoke \
--function-name process-order \
--payload fileb://order-event.json \
--cli-binary-format raw-in-base64-out \
order-response.json
View the response:
1
cat order-response.json
 
The response should contain the order data, the processing status, and the version of requests provided by the Layer.

10. Test Required-Field Validation

Now send an incomplete event:
1
2
3
4
5
cat > invalid-order-event.json <<'EOF'
{
"product_id": "SKU-8472"
}
EOF
Invoke the function:
1
2
3
4
5
aws lambda invoke \
--function-name process-order \
--payload fileb://invalid-order-event.json \
--cli-binary-format raw-in-base64-out \
invalid-order-response.json
Check the result:
1
cat invalid-order-response.json
The function should return a response with statusCode set to 400, indicating the missing fields.

11. Test the Customer Function

Create a valid event:
1
2
3
4
5
6
cat > customer-event.json <<'EOF'
{
"email": "ana@example.com",
"name": "Ana"
}
EOF
Invoke the function:
1
2
3
4
5
aws lambda invoke \
--function-name validate-customer \
--payload fileb://customer-event.json \
--cli-binary-format raw-in-base64-out \
customer-response.json
View the response:
1
cat customer-response.json
The response should include the customer data and the version of the requests library provided by the Layer.

12. Verify the Layer on the Function

To confirm the setup, check the Layers configured on the order function:
1
2
3
4
aws lambda get-function-configuration \
--function-name process-order \
--query 'Layers[*].Arn' \
--output text
Repeat this for the customer function:
1
2
3
4
aws lambda get-function-configuration \
--function-name validate-customer \
--query 'Layers[*].Arn' \
--output text
The commands should return the ARN of the Layer version associated with each function.

13. Understand How the Layer Becomes Available

At runtime, Lambda makes the Layer contents available in the /opt directory. For the Python runtime, the import paths are configured so that modules in the Layer can be found. That is why the handler can import them directly:
1
2
3
from shared_utils import standard_response, validate_request

import requests
 
These components do not need to be duplicated in the function ZIP because the Layer provides them.

14. List Layer Versions

List the published versions:
1
2
3
aws lambda list-layer-versions \
--layer-name shared-python-components \
--region "$AWS_REGION"
 
Each publication creates a new immutable version. If a function uses version 1, it will not automatically switch to version 2 when a new version is published. The update must be made explicitly.

15. Clean Up the Lab

To finish the lab, delete the functions:
1
2
3
4
5
aws lambda delete-function \
--function-name process-order

aws lambda delete-function \
--function-name validate-customer
Delete the published Layer version, and change the number if the version you created is different from 1:
1
2
3
4
aws lambda delete-layer-version \
--layer-name shared-python-components \
--version-number 1 \
--region "$AWS_REGION"
If you created the IAM role exclusively for this lab, detach the policy and delete the role:
1
2
3
4
5
6
aws iam detach-role-policy \
--role-name lambda-layers-lab-role \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

aws iam delete-role \
--role-name lambda-layers-lab-role
If you want, remove the files generated locally:
1
2
3
4
5
6
rm -rf lambda-layer
rm -f shared-python-components.zip
rm -f process-order.zip validate-customer.zip
rm -f trust-policy.json
rm -f order-event.json invalid-order-event.json customer-event.json
rm -f order-response.json invalid-order-response.json customer-response.json
Do not delete roles that are used by other labs or applications.

16. Limits and Quotas

A Lambda function can have up to five Layers attached. The combined unzipped size of the function package and its Layers must stay within the 250 MB limit.
This does not mean we need to create five Layers for every function. The separation should make sense for the architecture.
 
For example, a shared utilities Layer can serve multiple functions. Creating a separate Layer for every small file, on the other hand, can increase operational complexity.
 

17. When Does It Make Sense to Use Layers?

Some common scenarios:
  • Shared dependencies: multiple functions use the same library or set of libraries.
  • Reusable internal code: logging, validation, or response-handling modules are used by multiple functions.
  • Separation of responsibilities: the function package contains application logic, while the Layer holds shared components.
  • Controlled updates: a new version can be published and explicitly adopted by functions.

18. When Might a Layer Be Unnecessary?

If only one function uses a particular library, adding a Layer may not provide enough benefit to justify the extra complexity. Including the dependency directly in the function package may be simpler.
 
It is also important to consider dependency compatibility. Python libraries with native components must be compatible with the Linux environment and architecture used by Lambda. When needed, build the package in a compatible environment, for example, using Docker.
 

19. Reproducibility Considerations

Installing dependencies without pinning versions can produce different results across builds run at different times.
 
In this lab, requirements.txt specifies an explicit version:
1
requests==2.32.5
 
In real projects, keep dependencies updated in a controlled way and test compatibility before publishing a new Layer version. Pinning versions makes builds more predictable, but it does not replace testing or security reviews.

Conclusion

And that's the end of our lab!
 
Throughout this walkthrough, we created an AWS Lambda Layer containing the requests library and our own utility module, published a version of the Layer, and shared it between two Python Lambda functions. We also tested the functions to verify that the components provided by the Layer were available at runtime.
 
This showed us how to centralize shared dependencies without including them in every function package. In addition to reducing duplication, this approach helps organize and maintain code when different functions need the same components.
 
Now it's your turn: try adapting this lab to the dependencies and utilities you already use in your projects. It's a great way to reinforce what you've learned and see how Lambda Layers can simplify the development of your functions.
 
See you next time!

References

Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article