Automating AWS Lambda and Layer Deployments with GitHub Actions
š 2025-10-19 [UPDATED]
Managing and deploying AWS Lambda functions at scale can get complex, especially when dealing with multiple environments, configuration changes, and code versions. To simplify this, I created a GitHub Actions workflow that automates the deployment of both Lambda functions and Lambda layers ā directly from a monorepo structure, with smart caching and S3-backed hash tracking.
In this post, I'll walk you through how it works, the logic behind it, to help you adapting it to your own projects.
The repository uses a clear folder structure:
.
āāā functions/
ā āāā <app_name>/<function_name>/
ā āāā config.json
ā āāā [code files]
āāā layers/
ā āāā <layer_name>/
ā āāā config.json
ā āāā [dependencies]
āāā scripts/
ā āāā get-alias.sh
ā āāā install-packages.sh
ā āāā generate-function-hashes.sh
This allows us to independently track changes to each function or layer, and optimize deployments accordingly.
To authenticate GitHub Actions with AWS, I configured OpenID Connect (OIDC). This approach eliminates the need to store long-lived AWS credentials as GitHub secrets. It is the de facto standard for securely integrating third-party services with AWS using short-lived credentials. To set everything up, I followed the official GitHub documentation. Since I used environments for each AWS region in GitHub to enable multi-region Lambda deployments, I had to set the sub field to repo:ORG-NAME/REPO-NAME:environment:ENVIRONMENT-NAME.
The initial version of the tool didn't support deploying Lambda functions and layers to multiple AWS regions. To address this, I leveraged GitHub Environments, using environment-specific variables like S3 bucket names for each region. This setup also makes it easy to add environment-based approval steps, giving more control over how and when changes are deployed in each region.
The Deploy Lambda workflow runs on every push to any branch. Here's a breakdown of what it does:
jq for JSON parsingvars.Before proceeding, it pulls previously stored .code.hash and .config.hash files from S3 into a local cache. These are used to determine whether the Lambda function code and/or configuration has changed since the last deployment.
This enables smart, cache-aware deployments.
Using git diff and fallback logic for first commits, the workflow detects which folders under /layers have changed since the last commit. If this is the first commit, it defaults to all directories.
For each changed layer:
config.json for metadata like name, description, runtimesVersioning is automatic and timestamped, tied to the commit SHA.
Collect the function folders under /functions/<app>/<function>
Take a look at this example config.json file for a Lambda function located in the root of the function's folder. In this file you can define the function name, runtime, handler, IAM role, and the layers for each region where the Lambda function is deployed:
{
"function_name": "ArcadeLabContact",
"runtime": "nodejs22.x",
"handler": "index.handler",
"role": "ArcadeLabContactRole",
"layers": [
{
"eu-central-1": ["Axios:24", "Validator:22"],
"us-east-1": ["Axios:6", "Validator:6"]
}
]
}
In this configuration, you can specify the layers for each Lambda function in different regions, ensuring that reusable code can be applied across functions based on their deployment region.
For each changed Lambda function:
One key difference from the initial version is that this workflow now focuses solely on update logic. The creation of the Lambda function resources is managed separately through a Terraform repository called lambda-functions-tf. This separation is significant because it ensures that each Lambda function resource is controlled by Terraform, allowing for easy removal when needed.
Aliases (like dev, staging, etc.) are managed via the get-alias.sh script, enabling branch-to-alias mapping.
As mentioned earlier, the layer versions specified in the Lambda function's config file are assigned directly to the function. This setup enables a centrally managed layers repository, where reusable code can be stored and accessed by multiple functions.
As a final step, updated .code.hash and .config.hash files are uploaded back to S3, ensuring the next deployment has a reliable change reference.
The GitHub Actions (GHA) workflow is responsible for managing the deployment of Lambda functions and layers. Additionally, the lambda-functions-tf Terraform repository plays a key role with the following tasks:
Environment Setup: It provisions the necessary infrastructure for the GHA workflow, including IAM roles and policies, OIDC authentication, and S3 buckets for storing Lambda and layer code, along with Lambda config and code hashes.
Automatic Cleanup: It also configures resources to automate the cleanup of Lambda layers via EventBridge Scheduler.
Lambda Resources: Finally, it creates the Lambda-specific resources, such as the function itself and the associated IAM roles and policies.
It's important to note that while the Lambda function is created through Terraform, it is initially just a placeholder with no actual code. The real function code is deployed through the GHA workflow.
As mentioned earlier, for the lambda-functions repository, I leverage GitHub Environments to enable multi-region deployments. In the case of the lambda-functions-tf repo, I use Terraform modules and distinct providers for each region. This ensures that resources are deployed as expected and, if necessary, can be destroyed independently in each region.
| Feature | Description |
|---|---|
| Incremental Deploys | Only changed functions/layers are deployed |
| Environment-Aware | Automatically maps branch ā alias (e.g. dev, staging) |
| Smart Caching | Avoids unnecessary builds via S3-based hash comparison |
| Secure | Uses short-lived AWS credentials |
| Fully Idempotent | Can run multiple times without side effects |
This workflow has massively reduced friction in deploying AWS Lambda infrastructure at scale. If you're looking for a robust CI/CD setup for your serverless stack, this architecture can serve as a solid blueprint.
You can check out both repos here:
š https://github.com/denesbeck/lambda-functions
š https://github.com/denesbeck/lambda-functions-tf