How to Deploy a Gatsby Site to AWS with GitHub Actions
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.
{
"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.
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.
// ✂️ 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.
// ✂️ 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.
*.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.
// ✂️ 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.
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
.
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,
- A new build will be created.
- Our AWS credentials will be configured.
- The new build will pipe into S3.
- 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!