Skip to main content

Moving from Heroku Review Apps to AWS App Runner could be quite easy to do, let’s review it!

What is Heroku Review Apps?

A bit of background, Heroku is a container-based cloud Platform as a Service (PaaS). One of the major strength of Heroku has to offer is Review Apps, giving the opportunity to review your branches before they get merge in your GitHub repository. Review Apps is basically a collaboration tool that assist with providing an environment to showcase product changes.

Heroku has also many other possibilities and capabilities such as managing TLS Certificates, CI / CD pipelines, scaling on your behalf, so on and so forth.

At the end of the day, Heroku is the perfect example of practicing GitOps methodology, you can read more about it here.

AWS App Runner, what is it?

AWS App Runner has been introduced recently (may 2021) and offer a similar approach to Heroku Review Apps:

  • No orchestrators to configure
  • No need of pipelines
  • No Load Balancer to set up
  • Nor any servers to manage

As for Heroku, AWS App Runner can easily be linked to GitHub and create some new services based on your repository. Another advantage is to deploy automatically new containers on the fly, AWS Runner Apps can monitor your ECR (Elastic Container Registry) repository and deploy when a new container is available.

AWS Runner App, step by step

For this example, we will use a simple nodejs app with react on it, you can find more information on how to bootstrap your new repository here. Push your code then on the main branch, navigate to AWS Console and look for AWS App Runner:

Create a new App Runner Service
Select “source code repository” and add a new connection
Give a name to your connection and click on install another
Review and select the repository you wish to connect with AWS
After installation, click on Next and a new connection should appear on your AWS Console
Back on AWS Console, use Automatic deployment
In Build section, configure the Runtime, Build Command and Start Command
Give a name to your Service, and review the rest of the configuration
A new service should be created, it should take a few minutes
After a short period, navigate to the pre populated url and you should see the default react frontend

Automate App Runner with Terraform

We just saw how to deploy a simple application manually on AWS App Runner. Let’s see how to automate the deployment of Review Apps for each Pull Requests created by Devs. At that point in time, your Github repository should look like this:

08:00 $ ls -l
total 1024
-rw-r--r--     1 oli  staff    3362 25 Aug 09:59 README.md
-rw-r--r--     1 oli  staff     827 25 Aug 09:59 package.json
drwxr-xr-x     8 oli  staff     256 25 Aug 09:59 public
drwxr-xr-x    10 oli  staff     320 25 Aug 09:59 src
-rw-r--r--     1 oli  staff  510089 25 Aug 09:59 yarn.lock

We will use 2 different tools to automate the deployment of our Review Apps:

Terraform codes

Let’s start with Terraform part, create a folder called terraform at the root of your project, and add the following aws.tf file:

terraform {
  required_version = "~> 1.0"

  required_providers {
    aws = {
      source = "hashicorp/aws"
    }
  }

  backend "s3" {
    acl                  = "bucket-owner-full-control"
    bucket               = "ccl-s3-reviewapps-terraform-state-files"
    encrypt              = true
    workspace_key_prefix = "__KEY_PREFIX_NAME__"
    key                  = "__KEY_NAME__.tfstate"
    region               = "eu-west-1"
  }
}

A couple of explanation on the following code:

  • We are using terraform version 1
  • You need to create on own unique S3 bucket and replace ccl-s3-reviewapps-terraform-state-files bucket name
  • As we cannot use variables in Terraform backend block, we’re using these KEY_ which would be replace during the automation part on Github
  • More information about backend on this link

Add a new file in your terraform folder called app-runner.tf:

resource "aws_apprunner_service" "reviewapps" {
  service_name = "${var.name}-${var.stage}"

  source_configuration {
    authentication_configuration {
      connection_arn = "arn:aws:apprunner:eu-west-1:123456789012:connection/cdcloudlogix-connection/828d10e97715438293fb67db5af84ed7"
    }
    code_repository {
      code_configuration {
        code_configuration_values {
          build_command = "npm install"
          port          = var.port
          runtime       = "NODEJS_12"
          start_command = "npm start"
        }
        configuration_source = "API"
      }
      repository_url = "https://github.com/cdcloudlogix/ccl-my-app-fe-ar-deploy"
      source_code_version {
        type  = "BRANCH"
        value = var.branch
      }
    }
  }

  tags = {
    Name = "reviewapps-apprunner-service"
  }
}

A bit more information on this code:

  • connection_arn is the connection created previously during the manual creation (App Runner is fairly new in Terraform, there isn’t a way to collect data with Terraform just yet)
  • var.name, var.stage and var.branch would be provided by Github Actions later on
  • More explanation about this code here

Finally, create a variables.tf file as follow:

variable "name" {
  description = "(Required) Provide a name for reviewapps environment"
}

variable "stage" {
  description = "(Required) Stage, e.g. 'prod', 'staging', 'dev', OR 'source', 'build', 'test', 'deploy', 'release'"
}

variable "branch" {
  description = "(Required) Branch name on GitHub"
}

variable "port" {
  description = "(Optional) The port that your application listens to in the container. Default '3000'"
  default     = 3000
}

Github Actions steps

Before we proceed, be aware that you will need a system user in AWS with enough access to your account (Admin access as this is just testing). Generate Access and Secret keys and set them in Github secrets as follow:

Place your Access and Secret keys in Github secret of your repository

Place a new file in your repository under .github/workflows/deploy-reviewapps.yml as follow:

name: "Terraform"

on: pull_request

env:
  TF_IN_AUTOMATION: true

jobs:
  terraform-apply:
    name: Apply
    runs-on: ubuntu-latest
    env:
      KEY_PREFIX_NAME: ${{ github.event.repository.name }}
      KEY_NAME: ${{ github.event.number }}

    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - 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: ${{ secrets.AWS_DEFAULT_REGION }}

      - name: Adapt AWS Terraform Backend
        id: aws-backend-write
        run: |
          cd ./terraform
          sed -i -e "s/__KEY_PREFIX_NAME__/$KEY_PREFIX_NAME/g" ./aws.tf
          sed -i -e "s/__KEY_NAME__/$KEY_NAME/g" ./aws.tf
          cd ..

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v1
        with:
          terraform_version: '1.0.2'

      - name: Terraform Init
        run: terraform init
        working-directory: terraform

      - name: Terraform new Workspace
        run: terraform workspace select $workspace || terraform workspace new $workspace
        working-directory: terraform
        env:
          workspace: ${{ github.event.repository.name }}-pr-${{ github.event.number }}

      - name: Terraform Apply
        id: Apply
        if: github.event_name == 'pull_request'
        run: terraform apply -auto-approve -var 'name=${{ github.event.repository.name }}' -var 'stage=pr-${{ github.event.number }}' -var 'branch=${{ github.head_ref }}'
        working-directory: terraform
        env:
          TF_WORKSPACE: ${{ github.event.repository.name }}-pr-${{ github.event.number }}

A couple of explanation on this code:

  • We’re swapping KEY_PREFIX_NAME and KEY_NAME with the repository name and the PR number respectively on Terraform backend files.
  • We’re also creating a Terraform Workspace on the fly.
  • We’re pushing name, stage and branch variables at terraform apply stage.

Once you’ve done this, create a branch, modify the background-color of src/App.css file and create a new PR based on this change. You should see a new Github Action running as soon as you raise this Pull Request:

A new Github action started, it will execute Terraform Apply
A new service should appear in AWS App Runner
After a few minutes, you should be able to review your changes

Final part, the clean up process, add the following .github/workflows/destroy-reviewapps.yml file in your Github workflows:

name: Destroy review application on Amazon Elastic Beanstalk

on:
  pull_request:
    types: [closed, unlabeled]

jobs:
  terraform-destroy:
    name: Destroy
    runs-on: ubuntu-latest
    env:
      KEY_PREFIX_NAME: ${{ github.event.repository.name }}
      KEY_NAME: ${{ github.event.number }}

    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Adapt AWS Terraform Backend
        id: aws-backend-write
        run: |
          cd ./terraform
          sed -i -e "s/__KEY_PREFIX_NAME__/$KEY_PREFIX_NAME/g" ./aws.tf
          sed -i -e "s/__KEY_NAME__/$KEY_NAME/g" ./aws.tf
          cd ..

      - 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: ${{ secrets.AWS_DEFAULT_REGION }}

      - name: Terraform Init
        run: terraform init
        working-directory: terraform
        env:
          TF_WORKSPACE: ${{ github.event.repository.name }}-pr-${{ github.event.number }}

      - name: Terraform Destroy
        id: Destroy
        if: github.event_name == 'pull_request'
        run: terraform apply -destroy -auto-approve -var 'name=${{ github.event.repository.name }}' -var 'stage=pr-${{ github.event.number }}' -var 'branch=${{ github.head_ref }}'
        working-directory: terraform
        env:
          TF_WORKSPACE: ${{ github.event.repository.name }}-pr-${{ github.event.number }}

Once you close or merge your PR, Terraform would automatically remove your service from AWS.