Deploy to different environments using Github Actions

Written by Michael Anckaert - Published on - Posted in DevOps Tagged with Github CI/CD

In this post I will show you how you can deploy to different environments (think dev, staging, prod) using Github Actions.

The core problem we are solving is that the deployment must use specific AWS keys to access our dev, staging and prod environments. In my case, Github Environments aren't available yet. This means that Environment specific secrets can't be used. Since we need to set the correct AWS keys as environment variables for our deploy step, a work around was needed.

How we are going to solve this problem is that we will extract the branch name in our build job and place it in the outputs of that job. Then using that output value, we can add an if statement to our environment specific jobs.

In our build job, we do the following:

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      branch: ${{ steps.extract_branch.outputs.branch }}
    steps:
      - uses: actions/checkout@v2
      - name: Extract branch name
    shell: bash
    run: echo "::set-output name=branch::$(echo ${GITHUB_REF#refs/heads/})"
    id: extract_branch

The outputs section declares an output called branch that is the output of a step. (Output Inception!). In the step extract_branch, we use the set-output operator to set the value of the step output. So basically the output value goes from step -> output -> job -> output.

Next we can reference the output of the build job in the declaration of the other jobs:

deploy-dev:
  needs: build 
  runs-on: ubuntu-latest
  if: ${{ needs.build.outputs.branch == 'dev' }}

The if statement in the job declaration will cause the job to be skipped if the branch name doesn't equal the required branch name for that job. This way you can make the job dependent on the branch you pushed.

For your reference, here is the complete yaml declaration of the workflow. I modified one of my workflows to remove identifiable information, this might not work in a simple copy-paste.

name: Build and deploy our application

on:
  push:
    branches: [ dev, staging, prod]

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      branch: ${{ steps.extract_branch.outputs.branch }}
    steps:
      - uses: actions/checkout@v2
      - name: Extract branch name
    shell: bash
    run: echo "::set-output name=branch::$(echo ${GITHUB_REF#refs/heads/})"
    id: extract_branch
      - run: my-custom-build-script.sh
      - name: Upload built frontend files
    uses: actions/upload-artifact@v2
    with:
      name: app-build
      path: dist
      retention-days: 1
  deploy-dev:
    needs: build 
    runs-on: ubuntu-latest
    if: ${{ needs.build.outputs.branch == 'dev' }}
    steps:
      - name: Download built frontend files
    uses: actions/download-artifact@v2
    with:
      name: app-build
      path: dist
      - run: aws s3 sync . s3://app-${{ needs.build.outputs.branch}}-bucket
    working-directory: dist
    env: 
      AWS_ACCESS_KEY_ID: ${{ secrets.DEV_AWS_ACCESS_KEY_ID }}
      AWS_SECRET_ACCESS_KEY: ${{ secrets.DEV_AWS_SECRET_KEY }}
      AWS_DEFAULT_REGION: "eu-central-1"
  deploy-staging:
    needs: build 
    runs-on: ubuntu-latest
    if: ${{ needs.build.outputs.branch == 'staging' }}
    steps:
      - name: Download built frontend files
    uses: actions/download-artifact@v2
    with:
      name: app-build
      path: dist
      - run: aws s3 sync . s3://app-${{ needs.build.outputs.branch}}-bucket
    working-directory: dist
    env: 
      AWS_ACCESS_KEY_ID: ${{ secrets.STAGING_AWS_ACCESS_KEY_ID }}
      AWS_SECRET_ACCESS_KEY: ${{ secrets.STAGING_AWS_SECRET_KEY }}
      AWS_DEFAULT_REGION: "eu-central-1"
  deploy-prod:
    needs: build 
    runs-on: ubuntu-latest
    if: ${{ needs.build.outputs.branch == 'prod' }}
    steps:
      - name: Download built frontend files
    uses: actions/download-artifact@v2
    with:
      name: app-build
      path: dist
      - run: aws s3 sync . s3://app-${{ needs.build.outputs.branch}}-bucket
    working-directory: dist
    env: 
      AWS_ACCESS_KEY_ID: ${{ secrets.PROD_AWS_ACCESS_KEY_ID }}
      AWS_SECRET_ACCESS_KEY: ${{ secrets.PROD_AWS_SECRET_KEY }}
      AWS_DEFAULT_REGION: "eu-central-1"