A typical “dev” blog, and my attempt at getting my thoughts and learnings down on record. I’ve made attempts at this before and it’s always been left behind. I don’t write particularly well, and my actual job keeps me busy. But we’re giving this a go one more time.
This blog’s deployment pattern is your typical S3 static website. It’s an Astro site served from a private S3 bucket through CloudFront via OAC. Terraform defines the whole stack, a pull request runs the checks, and a push to main deploys.
GitHub repo connects to Astro build (push to main)
Astro build connects to CodeBuild runner (artifact)
CodeBuild runner connects to S3 bucket (s3 sync)
Reader connects to Route 53 (lookup)
Reader connects to CloudFront + WAF (request)
CloudFront + WAF connects to S3 bucket (origin)
FIG. 1 How a post gets from a Markdown file to a reader. Builds run on GitHub; only the deploy job holds AWS credentials. Boxes marked with a ring open the post that covers them.
What the CloudFront Free plan takes away, what it leaves you, and why I don't care.
Part 4Next
The deploy pipeline
Part 5Planned
What broke along the way
Decisions
What I chose, and why
01Chose a static site over a CMS, so every page is a file in the repo and there is no server to run.
02Kept the S3 bucket private and put CloudFront in front of it with Origin Access Control, so the only way to read the site is through the CDN.
03Used CloudFront's Free flat-rate plan. It includes a WAF but allows only five rules, managed response headers and no access logs, so the design fits inside those limits.
04Left DNS in the AWS account that already owns the domain and reached it from the site account through a second Terraform provider.
05Stored Terraform state in S3 with native locking instead of adding a DynamoDB table.
06Split the pipeline in two. GitHub builds the site with no AWS access; a CodeBuild-hosted runner deploys the finished artifact using a role scoped to one bucket and one distribution.
07Pinned every GitHub Action to a commit SHA, and made GitHub reject any that are not.