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: Buildanswers generated once
- control: Reviewread before shipping
- data: S3site and answers, OAC
- step: CloudFrontone origin, no CORS
- result: Answermatched in the browser
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
Move inference to build time when there is no server
CloudFront cannot authenticate to a Lambda function URL. Origin Access Control signs origin requests with UNSIGNED-PAYLOAD in place of a body hash, and a function URL rejects any signature whose hash does not match what it received. Measured both ways against the same endpoint: signed with the true payload hash it returns 200, signed with UNSIGNED-PAYLOAD it returns 403 and the function is never invoked. So the static deployment answers from content generated during the build instead of calling a model per request. That costs the ability to handle an unanticipated follow-up, and buys answers that are read before they ship, arrive instantly, and need no runtime, no key in the browser and no per-request cost.
- 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.