---
title: "How I deploy this site"
slug: /how-I-deploy-this-site/
date: 2023-01-02
tags: ["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`:

```js
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:

```bash
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:

```yaml
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
```
