Introduction
In this post, we’ll explore how to automate deployments to AWS Lambda using GitHub Actions, with Terraform as our Infrastructure as Code (IaC) tool for creating and managing AWS Lambda resources.
By the end of this post, you’ll be able to:
- Create an AWS Lambda function using Terraform.
- Set up a GitHub Actions pipeline to automate the deployment of the Lambda function.
- Trigger the Lambda function from the AWS console.
This guide assumes you’re already familiar with Terraform, GitHub Actions, and AWS Lambda. We’ll focus on deploying the Lambda function in an automated manner.
The source code referenced in this post is available on GitHub via the link below.
Step 1: Creating the Lambda Configuration with Terraform
In this section, we’ll create the configuration to deploy a basic Lambda function that runs Python code. The steps include:
- Setting up Terraform configuration for the Lambda function.
- Creating a CloudWatch Log Group for the function.
- Defining IAM policies and roles to allow Lambda to run and log events.
- Writing a basic Python function for Lambda.
1.1 Create Terraform Configuration for the Lambda Function
We’ll start by setting up the necessary resources to create a Lambda function:
- Create an S3 bucket to store the Lambda function code.
- Upload a zipped code package to the S3 bucket.
- Create the Lambda function that references the S3 object.
Replace all the placeholders with real values before running the code.
# S3 Bucket to store the lambda functions code
resource "aws_s3_bucket" "lambda_code_bucket" {
bucket = "<NAME_FOR_S3_BUCKET>"
}
# An S3 object holding the zipped code package
resource "aws_s3_object" "lambda_code" {
bucket = aws_s3_bucket.lambda_code_bucket.id
key = "<NAME_FOR_CODE_PACKAGE>"
source = var.code_zip_path
source_hash = filebase64sha256(var.code_zip_path)
}
# A basic Lambda function resource
resource "aws_lambda_function" "lambda" {
function_name = "<FUNCTION_NAME>"
s3_bucket = aws_s3_bucket.lambda_code_bucket.id
s3_key = aws_s3_object.lambda_code.key
role = aws_iam_role.lambda_role.arn
handler = "basic.main.handler"
runtime = "python3.10"
timeout = 30
memory_size = 128
package_type = "Zip"
# Ensures that the Lambda function is created after the log group is created
depends_on = [aws_cloudwatch_log_group.log_group]
# Ensures that the lambda function is updated when the S3 object hash changes
lifecycle {
replace_triggered_by = [aws_s3_object.lambda_code.source_hash]
}
}
1.2 Create Terraform Configuration for the Log Group
Next, we create a CloudWatch Log Group that the Lambda function will use for logging.
You can change log retention days by updating retention_in_days
.
# CloudWatch log group for the Lambda function
resource "aws_cloudwatch_log_group" "log_group" {
name = "/aws/lambda/<FUNCTION_NAME>"
retention_in_days = 30
}
1.3 Create IAM Policies for Lambda
Now, we need to set up the necessary IAM policies and roles for Lambda to write logs to CloudWatch and to allow Lambda execution.
# Policy to allow the Lambda function to write logs to CloudWatch Log Group
resource "aws_iam_policy" "lambda_logging_policy" {
name = "<FUNCTION_NAME>-logging-policy"
policy = jsonencode({
"Version" : "2012-10-17",
"Statement" : [
{
Action : [
"logs:CreateLogStream",
"logs:PutLogEvents"
],
Effect : "Allow",
Resource : "${aws_cloudwatch_log_group.log_group.arn}:*"
}
]
})
}
# Attach the policy to the Lambda role
resource "aws_iam_role_policy_attachment" "lambda_logging_policy_attachment" {
role = aws_iam_role.lambda_role.id
policy_arn = aws_iam_policy.lambda_logging_policy.arn
}
# Policy document for the Lambda role
data "aws_iam_policy_document" "lambda_role" {
statement {
effect = "Allow"
principals {
type = "Service"
identifiers = ["lambda.amazonaws.com"]
}
actions = ["sts:AssumeRole"]
}
}
# IAM role for the Lambda function
resource "aws_iam_role" "lambda_role" {
name = "<FUNCTION_NAME>-lambda-role"
assume_role_policy = data.aws_iam_policy_document.lambda_role.json
}
1.4 Write a Basic Python Function for Lambda
Finally, we’ll create a simple Python function that Lambda will execute.
Ensure the name of the function is passed to the Lambda handler. If the
function name is main
, the value for handler
attribute in the lambda
resource must be "basic.main.main"
.
def handler(event, context):
try:
return {"statusCode": 200, "body": "hello world"}
except Exception as e:
return {"statusCode": 400, "body": e}
Step 2: Setting Up GitHub Actions to Deploy the Lambda Function
In this section, we’ll configure a GitHub Actions pipeline with two jobs:
zip_lambda_code
– This job zips the Python code and uploads it as an artifact.deploy_lambda_function
– This job downloads the zipped code, configures AWS, and applies the Terraform configuration.
The pipeline runs when changes are pushed to the main
branch.
name: Deploy AWS Lambda Function
on:
push:
branches:
- main
defaults:
run:
working-directory: .
jobs:
zip_lambda_code:
runs-on: ubuntu-latest
steps:
- name: "Checkout"
uses: actions/checkout@v4
- name: "Zip Lambda Code"
run: |
zip demo.zip main.py
- name: "Upload Lambda Code"
uses: actions/upload-artifact@v3
with:
name: lambda_code
path: |
./demo.zip
deploy_lambda_function:
runs-on: ubuntu-latest
needs:
- zip_lambda_code
steps:
- name: "Checkout"
uses: actions/checkout@v4
- name: "Initialise AWS CLI"
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: "<AWS_REGION>"
aws-access-key-id: "<AWS_ACCESS_KEY_ID>"
aws-secret-access-key: "<AWS_SECRET_ACCESS_KEY>"
- name: "Download Lambda Code Zip"
uses: actions/download-artifact@v3
with:
name: lambda_code
path: .
- name: "Setup Terraform"
uses: hashicorp/setup-terraform@v3
with:
terraform_version: "1.9.5"
terraform_wrapper: true
- name: "Terraform Init"
run: terraform init -lock-timeout=15m
- name: "Terraform Plan"
run: terraform plan -out="plan.tfplan"
env:
TF_VAR_code_zip_path: demo.zip
- name: "Terraform Apply"
run: terraform apply -auto-approve plan.tfplan
Conclusion
In this guide, we automated the deployment of an AWS Lambda function using GitHub Actions and Terraform. We covered the essential steps needed to get started, but there are several ways you can improve this setup:
- Use
terraform validate
andterraform fmt
to lint your Terraform code. - Add unit tests for your Python code.
- Use GitHub Action OIDC for authentication with AWS instead of static access keys.
All the code from this post is available in the GitHub repository linked above.
Thank you for reading, and I hope you found this guide helpful!