Astro: CI/CD with AWS and Github Actions

Published on: Sun Dec 04 2022

Series

Content

Introduction

In this tutorial, we’re going to setup the CI/CD using Github Actions for our Astro static site deployments!

That way, whenever we push to our repository, it will automatically deploy our new changes to our AWS Infrastructure.

In addition, we will be using OpenID Connect (OIDC) so we don’t have to store the AWS credentials within Github Actions.

Oh, and all the infrastructure will be built using terraform!

Before we dive the building part, let’s go through a few more details on what is required to deploy our changes to AWS.

The Infrastructure

For this tutorial, we will only be working with Amazon S3 and Github actions as our CI/CD tool.

Later on, the goal is to also integrate Cloudfront into the whole infrastructure.

That way we can serve our assets within S3 from a location closer to our user.

The Astro build process

The pipeline

Here is what the automated CI/CD process would look like within Github actions.

Illustration of the Astro CI/CD with github actions (Just S3)
Illustration of the Astro CI/CD with github actions (Just S3)

The steps:

  1. Trigger - Github trigger (ie git push origin main )

  2. Build - Prepare the assets (This includes installing and building the Astro site)

  3. Upload - Upload the assets to Amazon S3

  4. Invalidation - Invalidate the cache on Cloudfront distribution (This will be covered in a later tutorial) ⚠️

Before you start

Before you start going through the tutorial, make sure you are using the starter - astro-aws-starter.

This streamlines some of the things like writing out all the boilerplate files.

It will be the base from which we will build from!

Building the pipeline

Now onto setting up the infrastructure.

1. Choose a name for the S3 bucket

Since S3 buckets need to be globally unique, you will need to change the name.

This S3 bucket is for holding the assets of our site.

So, within infra/main.tf , make the edit on the S3 resource name:

Add the following changes:

// infra/main.tf
resource "aws_s3_bucket" "site_asset_bucket" {
  bucket            = "static-site-assets-1234"
}

resource "aws_s3_bucket_acl" "site_asset_bucket_acl" {
  bucket = aws_s3_bucket.site_asset_bucket.id
  acl    = "private"
}

2. Add the Open ID connect provider

Add the following changes:

// infra/main.tf

data "tls_certificate" "github" {
  url = "https://token.actions.githubusercontent.com/.well-known/openid-configuration"
}

### Github OIDC for accessing AWS resources (ie S3) 
resource "aws_iam_openid_connect_provider" "github_actions" {
  client_id_list  = var.client_id_list
  thumbprint_list = [data.tls_certificate.github.certificates[0].sha1_fingerprint]
  url             = "https://token.actions.githubusercontent.com"
}

3. Create the IAM role for Github actions

This will be the IAM role used by our Github actions to manage AWS resources (ie S3).

// infra/main.tf

data "aws_caller_identity" "current" {}

data "aws_iam_policy_document" "github_actions_assume_role_policy" {
  statement {
    actions = ["sts:AssumeRole"]

    principals {
      type        = "AWS"
      identifiers = [
        format(
          "arn:aws:iam::%s:root",
          data.aws_caller_identity.current.account_id
        )
      ]
    }
  }

  statement {
    actions = ["sts:AssumeRoleWithWebIdentity"]
    principals {
      type = "Federated"
      identifiers = [
        format(
          "arn:aws:iam::%s:oidc-provider/token.actions.githubusercontent.com",
          data.aws_caller_identity.current.account_id
        )
      ]
    }
    condition {
      test     = "StringEquals"
      variable = "token.actions.githubusercontent.com:sub"
      values   = ["repo:${var.repo_name}:ref:refs/heads/main"]
    }
  }
}

resource "aws_iam_role" "github_actions" {
  name               = "github-actions"
  assume_role_policy = data.aws_iam_policy_document.github_actions_assume_role_policy.json
}

4. Create IAM policy permissions (data resource)

Now we have our role, we’ll need to add some permissions it.

Specifically to our use case, we need the ability to run the aws sync command to sync the resources created in our CI/CD and the S3 bucket.

// infra/main.tf

data "aws_iam_policy_document" "github_actions" {
  statement {
    actions = [
      "s3:GetBucketLocation",
      "s3:GetObject",
      "s3:ListBucket",
      "s3:PutObject",
      "s3:DeleteObject"
    ]
    effect = "Allow"
    resources = [
      aws_s3_bucket.site_asset_bucket.arn,
      "${aws_s3_bucket.site_asset_bucket.arn}/*"
    ]
  }
}

5. Attach the policy to the role

Now comes the final step, we need to attach IAM permissions to the IAM role we created.

// infra/main.tf

resource "aws_iam_role_policy" "github_actions" {
  name   = "github-actions"
  role   = aws_iam_role.github_actions.id
  policy = data.aws_iam_policy_document.github_actions.json
}

6. Uncomment the outputs

The outputs will show us the information of the generated resources (OIDC role arn, s3 bucket) we have defined when they are created.

// infra/ouputs.tf

# Output value definitions

output "site_asset_bucket" {
  description = "Name of the S3 bucket used to store function code."
  value = aws_s3_bucket.site_asset_bucket.id
}

output "role_arn" {
  value = aws_iam_role.github_actions.arn
}

# We will keep this one uncommented for now
#output "cf_distribution_domain_url" {
  #value = "https://${aws_cloudfront_distribution.cf_distribution.domain_name}"
#}

7. Update your repository name

Before you apply the infrastructure, we need to make sure the OIDC knows which Github repository that the role will be used in.

So, let’s update that.

// infra/variables.tf

variable "repo_name" {
  type    = string
  # Add the name of your respository in this field 👇
  default = "Jareechang/static-site-astro-aws-ci-cd"
}

8. Apply the infrastructure

Now we have all our definition in Terraform, all that is left is to generate it.

Run the following:

export AWS_ACCESS_KEY_ID=<your-key>
export AWS_SECRET_ACCESS_KEY=<your-secret>
export AWS_DEFAULT_REGION=us-east-1

terraform init
terraform plan
terraform apply -auto-approve

If the infrastructure applied successfully then you should see something like this:

Illustration of the Terraform outputs
Illustration of the Terraform outputs

Github actions

1. Add the secrets to your Github Repo

Illustration of the Github secrets UI
Illustration of the Github secrets UI

The secrets that we want to add to Github actions are:

  • AWS_S3_BUCKET_NAME - The S3 bucket we will upload assets to

  • UNSPLASH_API_KEY - Unsplash API key used by the Astro static site

2. Add the new workflow

Here is the yaml definition of our pipeline.

All of the deployment process is within scripts/deploy-site.sh .

Finally, be sure to update the role-to-assume from the terraform output.

name: deploy-site

on:
  push:
    branches:
      - master
      - main

# more changes
permissions:
  id-token: write
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Configure
        uses: actions/checkout@v2
      - uses: pnpm/action-setup@646cdf48217256a3d0b80361c5a50727664284f2
        with:
          version: 6.10.0
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@master
        with:
            role-to-assume: "<your-assume-role-arn>" 
            aws-region: "us-east-1"
      - run: ./scripts/deploy-site.sh
        env:
          AWS_S3_BUCKET_NAME: ${{ secrets.AWS_S3_BUCKET_NAME }}
          UNSPLASH_API_KEY: ${{ secrets.UNSPLASH_API_KEY }}

Testing it out

1. Push the changes

Push the changes to your github repository in the main branch.

git push origin main

2. Check the S3 bucket (in console)

If the whole build process went as expected, you should see the built assets in your S3 bucket.

If not, be sure to check out the logs in the github actions, and see if you missed anything or if there was a typo!

Illustration of the CI/CD built assets uploaded to S3 (AWS Console)
Illustration of the CI/CD built assets uploaded to S3 (AWS Console)

For reference, here is the repository of completed tutorial - Github: static-site-astro-aws-ci-cd.

Conclusion

Congrats! You now have a fully automated CI/CD for a static astro site.

You can view the site privately via the AWS console.

As of right now, the S3 bucket is private, it won’t be accessible publicly.

Then, in another tutorial, we will be setting up the whole flow with Cloudfront which will make our site accesible!

And... that’s all for now, stay tuned for more!

I hope you enjoyed this tutorial or learned something new.

If you did, please do share this article with a friend or co-worker 🙏❤️ (Thanks!)


Enjoy the content ?

Then consider signing up to get notified when new content arrives!

Jerry Chang 2023. All rights reserved.