Deploy Lambda to AWS using GH Actions
Step by step on how to deploy a lambda and lambda layer to AWS using GitHub Actions
Hello! Welcome to my new blog! =)
Generally, we need to use a tool for CI/CD to test and deploy our applications somewhere. In this post, I’ll show you how to deploy your NodeJS or Python application to AWS Lambda.
After you tested your code, it’s time to deploy it.
Let’s start for the simpler deploy of a Typescript application.
First, we need to build and zip the code, with the following GH Actions Job:
name: Deploy Production
on:
release:
types: [published]
env:
LAMBDA_NAME: myapp
PACKAGE_NAME: index.zip
jobs:
package-lambda-prd:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm run build
- name: Upload zip
uses: actions/upload-artifact@v2
with:
name: lambda
path: dist/$LAMBDA_ZIP
retention-days: 1
When this job finishes, we start the next one that uses a reusable workflow to update the lambda package to AWS:
update-lambda-prd:
needs: package-lambda-prd
uses: repository/github-actions-workflows/.github/workflows/lambda.yml
with:
zip_file: $LAMBDA_ZIP
lambda_name: $LAMBDA_NAME
artifact_name: lambda
secrets:
AWS_ACCESS_KEY_ID: ${{ secrets.PRD_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{secrets.PRD_AWS_SECRET_ACCESS_KEY }}
For the Python app the workflow is almost the same, but I do like to use a Lambda Layer to avoid the “You cannot see the code because your package is more than 3MB”. And with that, I dislike installing the boto31 dependency in the `poetry install` command, since the library is available in the Lambda Runtime environment.
For the layer, I use this workflow:
name: Deploy Layer Production
on:
release:
types: [published]
jobs:
package-layer-prd:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.9
- name: install poetry
run: curl -sSL https://install.python-poetry.org | python3 -
- name: package lambda
run: |
poetry build
poetry run pip install --upgrade -t python dist/*.whl
cd python
rm -rf boto3* botocore* project*
cd ..
zip -r layer.zip python -x '*.pyc'
- name: Upload zip
uses: actions/upload-artifact@v2
with:
name: layer
path: layer.zip
retention-days: 1
To update the Layer, I also use another reusable GitHub Workflow:
update-layer-prd:
needs: package-layer-prd
uses: repository/github-actions-workflows/.github/workflows/lambda-layer.yml
with:
zip_file: layer.zip
lambda_layer_name: lambda-layer
lambda_name: lambda-name
artifact_name: layer
runtime: python3.9
secrets:
AWS_ACCESS_KEY_ID: ${{ secrets.PRD_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.PRD_AWS_SECRET_ACCESS_KEY }}
Independent of the CI/CD tool that you use, it’s mandatory to be able to reuse CI/CD templates. I confess that I don’t like the way that GitHub actions works, and you can check another post that I wrote about this here.
Now, the reusable workflow for the lambda function that I use in update-lampda-prd
is:
name: Update lambda function
on:
workflow_call:
inputs:
zip_file:
type: string
required: true
description: Name of the zip file to be uploaded
lambda_name:
type: string
required: true
description: Name of the lambda to be updated
artifact_name:
type: string
required: true
description: Name of the artifact saved in previous steps that contains the zipfile
secrets:
AWS_ACCESS_KEY_ID:
description: AWS access key id
required: true
AWS_SECRET_ACCESS_KEY:
description: Aws secrets access key
required: true
jobs:
lambda:
runs-on: ubuntu-latest
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- uses: actions/download-artifact@v2
name: download zip artifact
with:
name: ${{ inputs.artifact_name }}
- name: Update lambda function
run: |
aws lambda update-function-code --function-name ${{ inputs.lambda_name }} --zip-file fileb://${{ inputs.zip_file }} --publish
Finally, the reusable workflow to deploy the Lambda Layer:
name: Update lambda layer
on:
workflow_call:
inputs:
zip_file:
type: string
required: true
description: Name of the zip file to be uploaded
lambda_name:
type: string
required: true
description: Name of the lambda to be updated
lambda_layer_name:
type: string
required: true
description: Name of the lambda to be updated
artifact_name:
type: string
required: true
description: Name of the artifact saved in previous steps that contains the zipfile
runtime:
type: string
required: true
description: Compatible runtime with this layer
secrets:
AWS_ACCESS_KEY_ID:
description: AWS access key id
required: true
AWS_SECRET_ACCESS_KEY:
description: Aws secrets access key
required: true
jobs:
lambda:
runs-on: ubuntu-latest
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- uses: actions/download-artifact@v2
name: download zip artifact
with:
name: ${{ inputs.artifact_name }}
- name: Update lambda layer
run: |
shopt -s lastpipe
echo Pushing new layer
CURR_DATE=$(date)
aws lambda publish-layer-version --description "$CURR_DATE" $LAYER_NAME $RUNTIMES $ARCHITECTURES $ZIP| jq -e -r '.LayerVersionArn' | read LAYER_ARN
echo Update lambda to use the new layer version
if [ -z "$LAYER_ARN" ]; then
echo layer arn is empty - skipping update
exit 1
else
echo layer arn is not empty - continue
fi
aws lambda update-function-configuration --function-name ${{inputs.lambda_name}} --layers $LAYER_ARN
env:
LAYER_NAME: --layer-name ${{ inputs.lambda_layer_name }}
RUNTIMES: --compatible-runtimes ${{ inputs.runtime }}
ARCHITECTURES: --compatible-architectures x86_64
ZIP: --zip-file fileb://${{inputs.zip_file}}
Tip: If you use GitLab, you can use this template to deploy the Lambda Code.
That’s all folks! See you next time!
I just realized that since I was using poetry I could add the boto3/botocore as developer dependency instead of runtime dep to avoid the `rm` command in the script like I do in the Typescript project.