Context
A personal site is usually a template. This one is the portfolio piece: the interesting work is the delivery path, not the markup.
The problem
The site had to ship two ways, as a container and as static files on S3, without maintaining two codebases. And the digital twin, which needs a live server, had to work on a static host where no server exists.
- step: Browsersame origin
- control: CloudFrontroutes by path
- data: S3static site, OAC
- step: Lambda/api/chat, IAM + OAC
- result: Streamed answersigned on the way out
Approach
- 01
One codebase, two build targets
A build flag switches between a standalone Node server for the container and a fully pre-rendered export for S3. Both are built on every CI run, so neither path rots.
- 02
Distroless runtime
The final image has no shell and no package manager, runs as a non-root user, and contains only the compiled server. The toolchain that built it never reaches the shipped layer.
- 03
Serve the AI endpoint from the edge, not a second domain
A streaming Lambda is attached to the same CloudFront distribution as a second origin, so the browser calls a same-origin path. No CORS, no second certificate. The function URL is IAM-authenticated behind Origin Access Control, so nobody can bypass the edge and spend the model credit directly.
- 04
Sign the assistant's own turns
The chat endpoint accepts conversation history from the browser. Checking that a message claims the assistant role proves nothing about who wrote it, so every answer is returned with an HMAC over its text and verified on replay. A fabricated prior turn is rejected before the model sees it.