Jérôme Decoster

Jérôme Decoster

3x AWS Certified - Architect, Developer, Cloud Practionner

18 Feb 2021

Lambda + Terraform + Github Actions

The Goal
  • Create a deployment pipeline for a Lambda function with Terraform
  • Use Lambda versions and aliases to define two environments for development and production
  • Use the API Gateway stageVariables to target one of these 2 environments
  • Use Gihub Actions to automatically update the Lambda function
  • A git push on the develop branch will update the dev version of the Lambda function
  • A git push on the master branch will update the prod version of the Lambda function


    Install and setup the project

    Get the code from this github repository :

    # download the code
    $ git clone \
        --depth 1 \
        https://github.com/jeromedecoster/lambda-terraform-github-actions.git \
    # cd
    $ cd /tmp/aws

    To setup the project, run the following command :

    # create env + terraform init
    $ make setup

    This command will :

    • Create an AWS user and put the access keys in the .env file
    • Initialize Terraform from the information defined in the infra directory

    Creating the architecture

    To create the architecture, run the following command :

    # terraform plan + apply
    $ make tf-apply

    This command will :

    The API Gateway is created :


    The /hello resource has a GET method that targets our Lambda :


    We can see in the Integration Request box that, by targeting the Lambda function, we associate a specific version defined by stageVariable :


    Two stages have also been published : dev and prod.

    Here is the source code to deploy the stage dev :

    resource "aws_api_gateway_deployment" "deployment_dev" {
      depends_on = [
      rest_api_id = aws_api_gateway_rest_api.api_gateway.id
    resource "aws_api_gateway_stage" "dev" {
      deployment_id = aws_api_gateway_deployment.deployment_dev.id
      rest_api_id   = aws_api_gateway_rest_api.api_gateway.id
      stage_name    = "dev"
      variables = {
        "stage" = "dev"

    The stage dev has a stageVariable called stage whose value is dev :


    The stage prod has a stageVariable called stage whose value is prod :


    Lambda function is created :


    The code is simple :


    Here is the source code to create the 2 aliases :

    resource "aws_lambda_alias" "alias_dev" {
      name             = "dev"
      description      = "dev"
      function_name    = aws_lambda_function.hello.arn
      function_version = "$LATEST"
    resource "aws_lambda_alias" "alias_prod" {
      name             = "prod"
      description      = "prod"
      function_name    = aws_lambda_function.hello.arn
      function_version = "$LATEST"

    We find these aliases in the web interface :


    We can test access to the Lambda function by a curl call :

    $ make hello-dev
    "Hello from Lambda"

    Setup up the Github project

    The project uses Github Actions to automatically deploy updates

    Lambda function automatic deployment requires AWS credentials

    Here is the source code using the accesses :

    - name: Configure AWS credentials
          uses: aws-actions/configure-aws-credentials@v1
            aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
            aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

    These accesses were created previously

    They are located in the .env file which is based on the .env.tmpl template

    We add these secrets in our project :


    The secrets have been added :


    Creating the develop branch

    As a reminder, the deployment of our project is done according to this principle :

    • A git push on the develop branch will update the dev version of the Lambda function

    • A git push on the master branch will update the prod version of the Lambda function

    At the moment we only have one master branch. So we create a develop branch from it :


    We edit the code of the Lambda function of the develop branch :


    We modify the message for Hello from develop :


    We commit directly to the develop branch :


    The cd.yml workflow starts :


    It ends quickly :


    To test our deployment we run the following command :

    $ make hello-dev

    This command executes a simple curl call :

    $ curl $(terraform output -raw hello_dev)

    It get the URL from a Terraform output :

    output "hello_dev" {
      value = "${aws_api_gateway_stage.dev.invoke_url}/hello"

    Our code modification, on the develop branch, returns the correct message :

    # successul return !
    $ make hello-dev
    "Hello from develop"

    Updating the master branch

    We quickly edit the master branch to test the proper functioning of our continuous deployment :


    The message is now Hello from master :


    We commit in the master branch :


    The cd.yml workflow starts :


    It ends quickly. To test our deployment we run the following command :

    $ make hello-prod
    "Hello from master"

    In the Lambda web interface, we can see that aliases now point to the published versions of a Lambda function and no longer to the $LATEST version :


    The prod and dev versions have an alias label :


    This publication and alias association is performed by this part of the publish.sh script :

    VERSION=$(aws lambda publish-version \
        --function-name $PROJECT_NAME \
        --description $1 \
        --region $AWS_REGION \
        --query Version \
        --output text)
    aws lambda create-alias \
        --function-name $PROJECT_NAME \
        --name $1 \
        --function-version $VERSION \
        --region $AWS_REGION

    Updating with the Github flow

    Our project works :

    • Updating the code on the master branch deploys a Lambda accessible via the URL /prod/hello

    • Updating the code on the develop branch deploys a Lambda accessible via the URL /dev/hello

    We now want to simulate a real update of our project using the Github flow.

    We will create a Pull Request to add a new feature to our function.

    We will name this feature feature-1.

    We edit our hello.js file from our develop branch :


    To simulate the first version of our feature-1, we modify our message to Hello with feature-1-v1 :


    By committing our code, we choose to create a new branch and name it feature-1 :


    The github web interface offers us to create a Pull Request.

    We name it feature-1 and we indicate that this branch should be merged into the develop branch :


    The pull request has been created but we do not merge it right away :


    We will first update our Lambda function :


    It is important to note that the previous commit on the new feature-1 branch did not trigger our continuous deployment workflow.

    This is because the workflow is configured to be triggered only after a push action on the master or the develop branches :

    name: cd
          - master
          - develop

    We are changing the message to Hello with feature-1-v2 :


    We commit this update in the feature-1 branch :


    We are now satisfied with all these modifications, we will be able to merge the pull request :


    The workflow is started :


    He finishes quickly :


    Our new version is quickly accessible from the stage dev :

    $ make hello-dev
    "Hello with feature-1-v2"

    If we are satisfied with this version, we can deploy it in production.

    We just need to create a new Pull Request to merge the develop branch to the master branch.

    The deployment will be automatic.