Platform
How this site runs
This portfolio is a small production system: static pages on a CDN, a serverless API for live stats, and a pipeline that ships every commit to main. The numbers below are fetched live from that API.
Live
connecting…
- Visits
- —
- API round-trip
- —
- AWS cost, month to date
- —
- Last deploy
- —
Recent deploys
| Commit | When | Duration | Status |
|---|---|---|---|
| This build: 56c18e4, built | |||
Architecture
Delivery pipeline
Every push to main runs one GitHub Actions workflow:
- Checkout & install.
npm cion Node 22 with cached dependencies - Build.
astro buildvalidates content schemas and renders every page and diagram to static HTML - Assume role (OIDC). GitHub → AWS STS web identity; short-lived credentials, no stored keys
- Sync to S3. Hashed assets get immutable caching; HTML is revalidated on every request
- Invalidate CloudFront. One wildcard invalidation, which is cheap because hashed assets are immutable and never go stale
- Record deploy. Writes SHA, duration and run URL to DynamoDB, which feeds the panel above
Guardrails
- Private origin. The S3 bucket blocks all public access. Only this CloudFront distribution can read it, through Origin Access Control.
- No long-lived keys. CI assumes an IAM role through GitHub OIDC, limited to this repository’s main branch.
- Security headers. A CloudFront response-headers policy sets HSTS, CSP, X-Content-Type-Options, frame-ancestors and Referrer-Policy.
- Least-privilege Lambda. The function can read and update its own DynamoDB table and read Cost Explorer, nothing else. The API has throttling limits.
- Everything in Terraform. DNS, certificates, CDN, API, IAM and alarms, with state in Terraform Cloud.
Why this shape
Static HTML is the cheapest, fastest and most secure thing to serve, so everything that can be decided at build time is: résumé content, case studies and diagrams. The only runtime piece is the small API behind this page. The earlier version of this site stored the résumé in DynamoDB and rendered it in the browser, which meant two copies of the truth that drifted apart. Now there's one JSON file, validated at build time.