skies.dev

How to Deploy a Gatsby Site to AWS with GitHub Actions

5 min read

How to Build and Deploy a Gatsby Site to AWS with GitHub Actions

In the previous tutorial, we set up all the infrastructure needed to host a Gatsby site on AWS. We defined our infrastructure as code using Terraform to help automate the provisioning of resources. We will be building off that previous tutorial.

Now, we'll build a continuous deployment pipeline to automatically deploy our Gatsby site to AWS using GitHub Actions.

To start, I recommend watching this excellent video by Guiding Digital to get some context on what we'll be doing. Make sure to give the video a like if you found it helpful!

Note, the video will be setting up IAM policies in the AWS console. We are going to create our policies using Terraform.

Setting Up an IAM Policy for GitHub to Deploy to AWS

We need to give GitHub Actions the ability to modify our S3 bucket and invalidate CloudFront.

We'll put our policy in a separate deploy.json.tpl file to decouple our policies from the Terraform scripts.

terraform/policies/deploy.json.tpl
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "StaticSiteDeployment",
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:GetObject",
        "s3:ListBucket",
        "s3:DeleteObject",
        "cloudfront:CreateInvalidation"
      ],
      "Resource": ["${cf_arn}", "${s3_arn}", "${s3_arn}/*"]
    }
  ]
}

Now, let's create iam.tf and load in this policy.

terraform/iam.tf
data "template_file" "static_website_deployment_document" {
  template = file("policies/deployment.json.tpl")

  vars = {
    cf_arn = aws_cloudfront_distribution.s3_distribution.arn
    s3_arn = aws_s3_bucket.my_bucket.arn
  }
}

resource "aws_iam_policy" "static_website_deployment_policy" {
  name = "static-website-deployment-policy"
  description = "Deploys Gatsby site to AWS S3 and CloudFront"
  policy = data.template_file.static_website_deployment_document.rendered
}

Next, we'll create the resource for the IAM policy and attach it to an IAM user.

terraform/iam.tf
// ✂️ Unchanged...

resource "aws_iam_policy" "static_website_deployment_policy" {
  name = "static-website-deployment-policy"
  description = "Deploys Gatsby site to AWS S3 and CloudFront"
  policy = data.template_file.static_website_deployment_document.rendered
}

resource "aws_iam_user" "static-website-deployment-user" {
  name = "static-website-deployment-user"
}

resource "aws_iam_user_policy_attachment" "attach_deployment_policy" {
  user = aws_iam_user.static-website-deployment-user.name
  policy_arn = aws_iam_policy.static_website_deployment_policy.arn
}

We also need the IAM access key to give GitHub so that it can deploy to AWS on our behalf.

terraform/iam.tf
// ✂️ Unchanged...

resource "aws_iam_access_key" "deployment_key" {
  user = aws_iam_user.static-website-deployment-user.name
}

output "secret" {
  value = aws_iam_access_key.deployment_key.encrypted_secret
}

At this point, be sure to .gitignore your tfstate files (if you haven't done so already) as they contain information about your IAM access key.

.gitignore
*.tfstate*

We should be able to apply the changes now to create the new IAM resources.

Providing Secrets for GitHub Actions

Now, we should be able to set up our secrets in GitHub. In GitHub, navigate to your repo's Settings and click the Secrets tab. We'll set the following secrets for our GitHub Action.

  • AWS_ACCESS_KEY_ID is the access key ID. This should be prefixed with "AKI".
  • AWS_BUCKET_NAME is the name of the S3 bucket we are hosting our site on.
  • AWS_CLOUDFRONT_DISTRIBUTION_ID is the CloudFront distribution ID.
  • AWS_REGION is the AWS region we are deploying to—we set this to us-east-1.
  • AWS_SECRET_ACCESS_KEY is the secret for our IAM user.

To figure out your IAM credentials, you will need to first apply the IAM policies we created in the earlier section. Once you applied the changes, a terraform.tfstate file should have been generated. Take a look at this file and find the state for aws_iam_access_key. This has the information we need to give GitHub.

terraform/terraform.tfstate
// ✂️ More state...

{
  "mode": "managed",
  "type": "aws_iam_access_key",
  "name": "deployment_key",
  "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
  "instances": [
    {
      "schema_version": 0,
      "attributes": {
        "encrypted_secret": null,
        "id": "AKI92V9G0SJFNAS0FPFK",
        "key_fingerprint": null,
        "pgp_key": null,
        "secret": "9fjDidpfIspdiJfnsoIsid+fisoISidjfnFSsd8w",
        "ses_smtp_password_v4": "jf9FjJLsilISpLsuyfYs67sLs8dsFjsnrLf/Fjsidpwf",
        "status": "Active",
        "user": "static-website-deployment-user"
      },
      "private": "fiIfpS==",
      "dependencies": ["aws_iam_user.static-website-deployment-user"]
    }
  ]
}

// ✂️ More state...

Note, the sensitive data you see in the example above is fake data for demonstration purposes.

Once you've provided GitHub with the necessary secrets, we can move on to creating our GitHub Action.

Create a GitHub Action to Deploy to a Gatsby Site to AWS S3 and CloudFront

We'll start by creating a .github/workflows/main.yml to house our GitHub Action. You don't have to call the script main.yml but the YAML script does need to be inside of .github/workflows/ in order for GitHub to use it.

Let's start by setting up the script to trigger a build whenever we push to the master branch or make a pull request against the master branch.

.github/workflows/main.yml
name: Deployment # the name doesn't matter—name it whatever you want
on:
  push:
    branches: [master]
  pull_request:
    branches: [master]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Build Website
        run: |
          yarn
          yarn build

Now, we'll set up the Action so that whenever we push to the master branch, we will deploy the new build to AWS.

We'll put an if check so that we don't deploy to AWS whenever there is a pull request against master.

.github/workflows/main.yml
name: Deployment
on:
  push:
    branches: [master]
  pull_request:
    branches: [master]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Build Website
        run: |
          yarn
          yarn build
      - name: Configure AWS credentials
        if: github.event_name == 'push'
        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_REGION }}
      - name: Deploy to S3
        if: github.event_name == 'push'
        run: aws s3 sync public s3://${{ secrets.AWS_BUCKET_NAME }} --delete
      - name: Invalidate CloudFront
        if: github.event_name == 'push'
        run: aws cloudfront create-invalidation --distribution-id ${{
          secrets.AWS_CLOUDFRONT_DISTRIBUTION_ID }} --paths "/*"

At this point, we should be ready to go. Whenever we push changes up to GitHub,

  1. A new build will be created.
  2. Our AWS credentials will be configured.
  3. The new build will pipe into S3.
  4. CloudFront will be invalidated.

I hope this tutorial helped you. If you think your network could benefit from this tutorial, then I would be grateful if you would share the article on social media. This would help me a lot!

Hey, you! 🫵

Did you know I created a YouTube channel? I'll be putting out a lot of new content on web development and software engineering so make sure to subscribe.

(clap if you liked the article)

You might also like