Skip to content
All projects
Platform engineering

This Site

A personal site built as a platform exercise: one codebase, two deployment targets, and an AI twin that cannot be forged.

Result
2
deploy targets, one codebase
Where
Personal
2026
Stack
7 tools
Next.js · Docker · Terraform · S3 · CloudFront · Lambda · DynamoDB

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.

  1. step: Buildanswers generated once
  2. control: Reviewread before shipping
  3. data: S3site and answers, OAC
  4. step: CloudFrontone origin, no CORS
  5. result: Answermatched in the browser
Inference happens once, during the build, because a bucket has no server.

Approach

  1. 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.

  2. 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.

  3. 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.

  4. 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.