systems-obscure/posts/how-I-deploy-this-site.md

4.9 KiB

title slug date tags
How I deploy this site /how-I-deploy-this-site/ 2023-01-02
log
linux

I want to make a note of how this blog is maintained and deployed for future reference as the AWS process is quite involved.

I use AWS because I have to know at least one cloud/serverless provider well and we use AWS at work. I intend to take the AWS Certified Developer exam eventually so knowing how to deploy a frontend application is useful.

Frontend: Gatsby.js

Nothing special here. I am using the React-based Gatsby framework to create the frontend and as you can see, there isn't much to the site. Just a homepage and a bunch of posts. I used the Gatsby starter template, changed the fonts and styled it in the manner of my AlienBlood theme.

Deployment: AWS S3, CloudFront, Route 53, Certificate Manager

  • First I uploaded the build directory to a bucket on S3 making sure to set the permissions to public and to specify that the bucket hosts a static website.
  • Then I created a hosted zone using AWS Route 53. This is necessary for the bucket to be publicly accessible as a domain on the internet. I purchased the domain name from GoDaddy and uploaded the AWS nameservers there. Cue 24 hours propagation time.
  • All sites should be served over https so I requested a public SSL certificate from AWS Certificate Manager.
  • You can't serve an S3 bucket as https by default. The solution is to use CloudFront as a CDN and then specify a redirection rule from http to https. So you create the CloudFont instance and generate an endpoint. This endpoint is then added to the Route 53 specifications as an A record. That propagates in a matter of seconds and now the site is securely hosted.

Middlewear: AWS Lambda

An annoying thing about CloudFront is that it won't recognise pages other than the root index.html on refresh. So if I went to this page (https://systemsobscure.blog/how-I-deploy-this-site/), on refresh it becomes a 403 because the file format is not how-I-deploy_this_site/index.html.

To get around this the custom is to use a Lambda function that intercepts requests and add the trailing index.html:

exports.handler = (event, context, callback) => {
  // Extract the request from the CloudFront event that is sent to Lambda@Edge
  var request = event.Records[0].cf.request

  // Extract the URI from the request
  var olduri = request.uri

  // Match any '/' that occurs at the end of a URI. Replace it with a default index
  var newuri = olduri.replace(/\/$/, "/index.html")

  // Replace the received URI with the URI that includes the index page
  request.uri = newuri

  // Return to CloudFront
  return callback(null, request)
}

You then set this function to trigger on page requests to the CloudFront distribution that is handling the routing and the problem is resolved.

Continuous deployment: GitHub Actions

It's slightly onerous to have to manually trigger a deploy to S3 every time I write a new post. A better scenario would be to trigger a build and a deployment to S3 every time I push to the main branch on GitHub. I could do this from within AWS but I've chosen to have GitHub communicate with AWS rather than the other way around. That way I get some experience of using Actions.

My GitHub Action declaration runs the standard gatsby build command on push and also runs the following NPM script once the build completes:

gatsby-plugin-s3 deploy --yes; aws cloudfront create-invalidation --distribution-id <REDACTED> --paths '/*';",

This uses a Gatsby plugin to deploy to S3 and clear the Cloudfront cache.

In order for this command to run from GitHub I had to create a "GitHub" user and custom permissions file in AWS IAM. This gives me an Access Key ID and secret which the GitHub Action can use to authenticate the deployment. I save these as secrets within the repository settings in GitHub and now the whole Action declaration works.

Here is the GitHub Action in full:

name: Deploy systemsobscure.blog
on:
  push:
    branches:
      - main
jobs:
  build:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
        with:
          node-version: 18
      - name: Caching Gatsby
        id: gatsby-cache-build
        uses: actions/cache@v2
        with:
          path: |
            public
            .cache
            node_modules
          key: ${{ runner.os }}-systemsobscure-site-build-${{ github.run_id }}
          restore-keys: |
            ${{ runner.os }}-systemsobscure-site-build-
      - name: Install dependencies
        run: npm install
      - name: Build
        run: npm run build
      - name: Set 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: eu-west-1
      - name: Deploy to S3
        run: npm run deploy