UPDATED: Deploying a static site to AWS using GitHub Actions

Hero image for post UPDATED: Deploying a static site to AWS using GitHub Actions

Photo by Yancy Min on Unsplash

It has been just under a year since I wrote my initial blog post about deploying static sites to AWS via GitHub Actions and a number of things have changed since then. Github Actions has now been generally available for some time and although number of the referenced actions have changed, the main principles remain the same. In this post I’ll explain my process for deploying static sites to AWS using Github Actions in greater detail and whats changed since the initial version of the article.

If you’re just interested in the build template, it is posted in its entirety at the end of the article.

Assumed infrastructure

The template assumes you are using a privately hosted S3 static site and serving the content using CloudFront. You could just as easily use a public S3 static site and omit the Invalidate Cloudfront CDN step.

Assumed infrastructure layout

Breaking down the build template

One of the more conventional approaches to continuous integration builds is breaking it down into 3 distinct steps, test, build and deploy. Doing so allows us to easily pinpoint at a glance where things have gone wrong when the build fails. In my previous build template, I had combined the build and deploy steps because at that point it was unclear how to share assets between jobs. However this changed upon discovering the actions/upload-artifact and actions/download-artifact actions.

The test job

The test job is fairly straightforward, it simply checks out the code, installs the dependencies and runs the tests. Not much to see here.

test:
  runs-on: ubuntu-latest

  steps:
    - uses: actions/checkout@v2

    - name: Install dependencies
      run: npm ci

    - name: Run developer tests
      run: npm test

The build job

This is one of the differences from the previous article, I am splitting up the build and deploy jobs since I now know how to manage build artifacts between steps. In this step, I again check out the code and install dependencies then follow it by building the site. Next, I upload the build artifact to GitHub using the actions/upload-artifact action specifying the build folder as our source and naming it frontend-artifact. This name will be used later on as well as showing in the GitHub interface.

build:
  runs-on: ubuntu-latest

  steps:
    - uses: actions/checkout@v2

    - name: Install dependencies
      run: npm ci

    - name: Build frontend
      run: npm run build

    - uses: actions/upload-artifact@v2
      with:
        name: frontend-artifact
        path: build

The frontend artifact showing in the GitHub interface

The deploy job

The deploy job is the other big difference from the previous article since it now uses the aws-actions/configure-aws-credentials action to authenticate with AWS and then perform operations. Firstly, I set 2 conditions for running the deploy job; that we only run on the master branch and not on other branches, and we only run after successfully running the test and build jobs. This is simply because I only want to deploy code I am happy to merge into the main branch which has passed all the tests and successfully builds.

I start by downloading the frontend-artifact that I uploaded in the build step, then I configure my access to AWS using GitHub secrets (see Keeping your secrets safe in the previous article) before uploading the code to S3 and invalidating the CloudFront cache so that it knows about the most recent version of the site.

deploy:
  needs: [test, build]
  runs-on: ubuntu-latest
  if: github.ref == 'refs/heads/master'

  steps:
    - uses: actions/download-artifact@v2
      with:
        name: frontend-artifact

    - 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: <<YOUR REGION HERE>>

    - name: Deploy to S3 Bucket
      run: aws s3 sync ./ s3://<<YOUR S3 BUCKET NAME HERE>>

    - name: Invalidate Cloudfront CDN
      run: aws cloudfront create-invalidation --distribution-id=$CLOUDFRONT_DISTRIBUTION_ID --paths '/*'
      env:
        CLOUDFRONT_DISTRIBUTION_ID: <<YOUR CLOUDFRONT DISTRIBUTION ID HERE>>

Putting it all together

So thats all the individual build jobs and a bit of a breakdown of each, the full template is below. There are 2 points worth mentioning about the full template; we are running it on each push and pull request so that we are informed of any issues sooner and the test and build steps run concurrently despite having to install dependencies each time. The reason for this is simply to get quick feedback and this way I don’t have to deal with any form of hand off between each of the stages.

I hope you’ve found this post useful and now have an idea of how easy it is to deploy you static sites to AWS using GitHub Actions.

name: Frontend CI

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2

      - name: Install dependencies
        run: npm ci

      - name: Run developer tests
        run: npm test

  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2

      - name: Install dependencies
        run: npm ci

      - name: Build frontend
        run: npm run build

      - uses: actions/upload-artifact@v2
        with:
          name: frontend-artifact
          path: build

  deploy:
    needs: [test, build]
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/master'

    steps:
      - uses: actions/download-artifact@v2
        with:
          name: frontend-artifact

      - 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: <<YOUR REGION HERE>>

      - name: Deploy to S3 Bucket
        run: aws s3 sync ./ s3://<<YOUR S3 BUCKET NAME HERE>>

      - name: Invalidate Cloudfront CDN
        run: aws cloudfront create-invalidation --distribution-id=$CLOUDFRONT_DISTRIBUTION_ID --paths '/*'
        env:
          CLOUDFRONT_DISTRIBUTION_ID: <<YOUR CLOUDFRONT DISTRIBUTION ID HERE>>
← Back to the blog