The source for millsymills.com: an Astro static site hosted on AWS, with Terraform for the infrastructure and GitHub Actions for CI/CD.
Released under MIT as a community template. Fork it, rename it, ship your own personal site on AWS.
src/: Astro pages and layouts (output: 'static').infra/: Terraform for S3 (private, OAC-fronted), CloudFront (HTTPS, security headers, directory-index rewrite), Route53 (apex +www, IPv4 and IPv6), ACM (us-east-1), IAM OIDC deploy role, and ProtonMail email DNS (SPF/DKIM/DMARC)..github/workflows/: CI (Astro build + typecheck + Terraform fmt/validate) and deploy. Per-PR / per-push CI triggers are currently disabled (run./scripts/ci-local.shlocally instead); the deploy workflow runs onworkflow_dispatchplus a monthlyscheduleso/.well-known/security.txt's 12-monthExpires:field can't silently go stale. Deploys are unattended on the GitHub free-private plan (which does not support Environment required-reviewer protection); the trust boundary is OIDCsub+job_workflow_refpinned to a specific workflow file, plus a tightly-scoped IAM role. See.github/workflows/deploy.yml's header comment andinfra/github_oidc.tf.
npm install
npm run dev # localhost:4321
npm run build # static output to dist/
npm run preview # preview the built site
npx astro check # typecheck Astro filesNode 22+ required.
One-time prerequisites:
- A Route53 public hosted zone for your domain (created manually; Terraform reads it via a data source).
- An S3 bucket for Terraform state (e.g.
<domain>-terraform-state) with versioning and SSE-S3 enabled. Thebackend "s3" {}block ininfra/main.tfis already wired; all backend fields (bucket, key, region, encrypt, use_lockfile) are supplied per-stack viaterraform init -backend-config=.... Seeinfra/stacks/*.backend.hcl. - Terraform 1.10+ installed.
Then, using the stack-aware wrapper (which supplies -backend-config for you and refuses to touch the wrong stack):
./scripts/tf.sh <stack> init # e.g. ./scripts/tf.sh millsymills init
./scripts/tf.sh <stack> plan
./scripts/tf.sh <stack> applyStacks are defined under infra/stacks/. Forks should add a <stack>.tfvars + <stack>.backend.hcl pair for their own domain.
See CLAUDE.md for the full migration / deploy / email runbook, including:
- Cutting over from an existing host (e.g. Squarespace) to the AWS stack.
- Wiring up the GitHub Actions deploy (OIDC role, env-scoped variables, OIDC sub-claim trust pin).
- Activating ProtonMail custom domain email (verification token → DKIM → mailboxes).
MIT. Copyright mills, 2026.