Skip to content
shoults.ai

how i built thisThe site is the résumé

A portfolio site can claim you know cloud architecture, or it can be cloud architecture. This one is the latter: every layer below is real, minimal, and public. Read the source.

GitHub ActionsOIDC · no keysS3 bucketprivate · OACCloudFrontACM TLS · edge fnRoute 53shoults.aisyncoriginalias
Deploys flow left to right; visitors resolve right to left. Defined in ~200 lines of CDK.

Stack decisions

Framework
Astro, static output, zero client-side framework. The only shipped JS is the knowledge graph (d3-force physics + hand-rolled SVG).
Hosting
Private S3 bucket behind CloudFront with Origin Access Control. The bucket is never public; the CDN is the only reader.
TLS & DNS
ACM certificate (DNS-validated) and Route 53 alias records for apex + www.
Edge logic
A CloudFront viewer-request function maps clean URLs to S3 object keys and 301s www to the apex domain.
Infrastructure as code
Everything above is AWS CDK (TypeScript), unit-tested with CloudFormation template assertions, in this public repo.
CI/CD
GitHub Actions federates into AWS via OIDC: a least-privilege role that can only sync one bucket and invalidate one distribution. No stored cloud keys.
AI pair
Claude Code wrote the first draft of nearly every file, working from a reviewed architecture plan. Design decisions and review stayed human.

Build log

Day one: plan first, then scaffold, then IaC

2026-07-24 · planning · cdk · astro · claude-code

The site started as a planning session, not a create-app command. Decisions made before any code: Astro with zero client frameworks, hosting on AWS because the hosting is the portfolio (private S3 origin + CloudFront + Route 53, all CDK TypeScript), keyless deploys from GitHub Actions via OIDC, and a hard rule that employer-proprietary material never enters this repo, enforced by a local disclosure-check script rather than good intentions.

Day-one output: monorepo scaffold (site/ Astro, infra/ CDK), both CDK stacks written and unit-tested before the AWS account had credentials, CI workflow, design tokens, and the first cut of the knowledge graph. Claude Code wrote the first draft of everything; the architecture decisions came from a human who has to defend them in interviews.

Real content, and a graph with a point of view

2026-07-24 · content · knowledge-graph · positioning

The skeleton became a site. Experience, speaking topics, and eight real past talks were authored from private source materials (resume, reviews, work summaries) that never entered this repo. Everything public was generalized to resume level and run through the disclosure check before commit; a local banned-terms list now guards internal codenames, deal names, and colleague names against ever slipping in.

The knowledge graph got rebuilt around three pillars: AI Engineering, Cloud Architecture, and Stats & Epidemiology. A fourth group of cross-cutting practices (responsible AI, executive communication, leadership, strategy) bridges them. That structure is the actual argument the site makes. Not a cloud architect; not only an AI engineer; an AI executive whose range is the point. With 28 nodes the layout needed real tuning: stronger repulsion, a bigger canvas, and hub-only labels that keep the three pillars legible while the rest reveal on hover.

Picking the domain: shoults.ai over shoults.engineer

2026-07-24 · domain · branding · privacy

The site changed names before it ever went live. .engineer was cheaper and reinforced the hands-on-builder half of the story, but the positioning here is AI-executive-first, and shoults.ai matches what the site actually argues.

One real catch drove the decision process: .ai is Anguilla’s country-code TLD, and its registry has historically not supported WHOIS privacy the way standard gTLDs like .engineer do. Rather than expose a real home address and phone number, the fix was proxy registrant details: a forwarding number, a mailbox service address, and an email alias.

On the code side, switching domains touched about six places, so the domain now derives from a single config value (SITE.domain / siteConfig.domainName). The header brand, the architecture diagram, robots.txt (now a dynamic endpoint), JSON-LD, and the CDK stack all read from there. The next domain change, if it ever happens, is one line.