Skip to content

Platform Architecture

The repository behind this site is a three-part platform — content delivery, visitor metrics, and the Terraform control plane behind both. These are the same diagrams the repo README carries; this page gives them a home on the site. The Site Structure map shows where pages live; this page shows how the platform works.

Site — content delivery

flowchart LR
    subgraph Local[dev]
        DEV[podman-compose · serve.py<br/>HTTPS via mkcert]
    end
    subgraph GHA[GitHub Actions]
        B[ci.yml — build + checks] -->|main| D1[deploy-staging-s3]
        B -->|v* tag| D2[deploy-pre-prod-s3]
        B -->|v* tag| D3[deploy-prod-pages]
    end
    D1 --> STG[staging — S3 + CloudFront · OAC]
    D2 --> PRE[pre-prod — S3 + CloudFront · OAC]
    D3 --> PRD[prod — GitHub Pages]

Prod is gated: the Pages deploy runs behind a required reviewer in the prod GitHub environment — the AWS mirror (pre-prod) lands first, then Pages ships on approval. Two delivery planes, each with its own gate: contentmain → staging · v* → pre-prod → gated Pages; infrastructuremain → staging auto-applies · v* → prod plan-only.

Metrics — visitor analytics

flowchart LR
    V[site visitor] -->|POST /event| CF[CloudFront<br/>geo headers]
    CF --> GW[API Gateway]
    GW -->|POST /event| W[Lambda — writer]
    GW -->|GET /summary · /views · /health| R[Lambda — reader]
    W -->|PutItem| DB[(DynamoDB<br/>TTL 90 days)]
    R -->|Scan · Query| DB

CloudFront supplies the geo headers, so no IP address ever reaches the Lambda (why?). Writer and reader lambdas each have their own least-privilege role; the API is public but origin-gated. staging runs its own stack; pre-prod + prod share one; dev runs Ministack (no edge). Raw events expire after 90 days.

Terraform — the control plane

flowchart TB
    BOOT[terraform/ci — bootstrap<br/>manual · run as an AWS user] --> STATE[(state backends<br/>S3 + DynamoDB lock<br/>staging · prod · local dev)]
    BOOT --> ROLES[OIDC roles — least privilege, one per job<br/>-terraform · -deploy · -invalidate · -toggle]
    WORK[GitHub Actions workflows] -->|assume role| ROLES
    ROLES -->|plan · apply · sync| STACKS[site + metrics stacks<br/>staging · prod]

terraform/ci creates the per-environment state backends and the OIDC roles GitHub Actions assumes to build and run the stacks. Bootstrap is the one out-of-band step — an AWS user, outside GitHub Actions, creates them; no workflow ever uses keys.

How a change ships

flowchart LR
    M[push / PR to main] --> C{required checks<br/>per-surface · skip-model}
    V[v* tag<br/>ruleset-gated] --> C
    C -->|pass| B[Build — ci.yml]
    V --> B
    B --> A[site artifact]
    A -->|workflow_run · main| S[deploy → staging env]
    A -->|workflow_run · v*| P[deploy → pre-prod → gated prod]
    V --> R[release — tag + SBOM]
    T[tf change] --> TP[terraform plan] -->|manual apply| AP[apply]
    X[workflow_dispatch] --> TG[toggle-env] & INV[invalidate]

Implementation reference (every workflow, role, and operational extra): .github/workflows/README.md.