Published on: Sun Dec 04 2022
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.
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.
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)
The steps:
Trigger - Github trigger (ie git push origin main
)
Build - Prepare the assets (This includes installing and building the Astro site)
Upload - Upload the assets to Amazon S3
Invalidation - Invalidate the cache on Cloudfront distribution (This will be covered in a later tutorial) ⚠️
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!
Now onto setting up the infrastructure.
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"
}
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"
}
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
}
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}/*"
]
}
}
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
}
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}"
#}
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"
}
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 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
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 }}
Push the changes to your github repository in the main
branch.
git push origin main
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)
For reference, here is the repository of completed tutorial - Github: static-site-astro-aws-ci-cd.
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!)
Then consider signing up to get notified when new content arrives!