mirror of
https://github.com/hanzoai/id.git
synced 2026-10-16 18:38:15 +00:00
Today's sign-in outage: during the 0.2.34 rollout a module fetch for the new bundle hit an old replica, the bare 404 had no cache-control, and Cloudflare's edge cached it for four hours. hanzo.id served a black page with a healthy origin behind it. spa 1.4.11 marks asset-miss 404s no-store (and reunites the spa lineage that shipped as 1.4.3-1.4.8 from a branch no remote called main). Co-authored-by: Hanzo Dev <[email protected]>
98 lines
5.1 KiB
Docker
98 lines
5.1 KiB
Docker
# syntax=docker/dockerfile:1.7
|
|
# Hanzo ID — Vite SPA built once, served by hanzoai/spa.
|
|
FROM node:24-alpine AS build
|
|
WORKDIR /build
|
|
ENV PNPM_HOME=/pnpm PATH=$PNPM_HOME:$PATH
|
|
RUN corepack enable && corepack prepare [email protected] --activate
|
|
|
|
# THE LOCKFILE SHIPS, and the install is frozen to it.
|
|
#
|
|
# This used to omit pnpm-lock.yaml and run `--frozen-lockfile=false`, so the
|
|
# image resolved the whole tree FRESH on every build while `pnpm test` on the
|
|
# runner resolved it from the lockfile. Two different dependency graphs from one
|
|
# commit: the tested one, and the shipped one. It went green for as long as free
|
|
# resolution happened to agree, and stopped the moment it did not — adding one
|
|
# dependency (@hanzo/event) moved vite from the lockfile's
|
|
# 7.3.5_@[email protected]_… to 7.3.6_@[email protected] and the build died on
|
|
# `Cannot find module '/build/apps/web/node_modules/vite/bin/vite.js'`. Nothing
|
|
# was wrong with the source: the same commit builds cleanly when installed from
|
|
# the lockfile.
|
|
#
|
|
# A resolver free to drift ships a bundle no one has run. Frozen, the image gets
|
|
# the exact tree the tests passed against, and a lockfile that has gone stale
|
|
# fails HERE — loudly, naming the mismatch — instead of silently building
|
|
# something else.
|
|
#
|
|
# EVERY workspace member's package.json must be present before a frozen install:
|
|
# pnpm validates the lockfile against all of them and refuses if one is missing.
|
|
# apps/account is not built into this image, but it IS in the workspace, so its
|
|
# manifest is required for the check to pass.
|
|
COPY pnpm-workspace.yaml pnpm-lock.yaml package.json tsconfig.base.json ./
|
|
COPY apps/web/package.json apps/web/
|
|
COPY apps/account/package.json apps/account/
|
|
COPY pkgs/shared/package.json pkgs/shared/
|
|
COPY pkgs/auth/package.json pkgs/auth/
|
|
COPY pkgs/connect/package.json pkgs/connect/
|
|
COPY pkgs/idv/package.json pkgs/idv/
|
|
COPY pkgs/onboarding/package.json pkgs/onboarding/
|
|
RUN pnpm install --frozen-lockfile
|
|
|
|
COPY apps apps
|
|
COPY pkgs pkgs
|
|
|
|
# Publishable event-ingest key (pk-live-…), inlined by Vite into the bundle.
|
|
#
|
|
# PUBLISHABLE_KEY is the name in KMS (org `hanzo`, path `deploy`, env `prod`)
|
|
# and on the --build-arg; the VITE_ prefix is what makes Vite inline it, and it
|
|
# is a property of THIS build, so it is applied here and the secret store keeps
|
|
# the ONE plain name.
|
|
#
|
|
# Publishable and write-only by design — it authorizes a write into one org and
|
|
# can read nothing — so shipping it in a bundle is the documented use. It is
|
|
# still a credential: it comes from KMS via CI. Never commit a value here.
|
|
#
|
|
# Deliberately NO default. An absent key is not a degraded mode: the door refuses
|
|
# the beacon outright. Measured against the live endpoint with and without a
|
|
# browser Origin, for pageviews AND exceptions alike: a hard 401
|
|
# `ingest_key_required`. Nothing arrives.
|
|
#
|
|
# This comment used to say the unkeyed beacon took an "anonymous lane" that filed
|
|
# rows under a `$public` tenant and answered 200. That lane is not implemented in
|
|
# the deployed cloud, and the belief was expensive: it is why "we still get
|
|
# errors, just not events" was accepted across four repos while the true answer
|
|
# was that a keyless surface reports nothing at all. hanzo.id ran exactly that
|
|
# way, which is the failure this gate exists to make loud.
|
|
ARG PUBLISHABLE_KEY
|
|
ENV VITE_PUBLISHABLE_KEY=$PUBLISHABLE_KEY
|
|
# Fail closed, and gate HERE because this is the one path every builder passes
|
|
# through — a guard in a workflow protects that lane only.
|
|
RUN case "$PUBLISHABLE_KEY" in \
|
|
pk-*) : ;; \
|
|
'') echo "PUBLISHABLE_KEY is empty - pass --build-arg PUBLISHABLE_KEY=<pk-...> (KMS deploy/PUBLISHABLE_KEY, env prod)" >&2; exit 1 ;; \
|
|
*) echo "PUBLISHABLE_KEY is not a publishable key (expected a pk- prefix)" >&2; exit 1 ;; \
|
|
esac
|
|
|
|
# Do NOT re-declare ARG VITE_PUBLISHABLE_KEY below this line. A later ARG of the
|
|
# same name shadows the ENV set above with an empty default, so the key resolves,
|
|
# passes the gate, and is then blanked before Vite inlines it — every step green,
|
|
# the bundle unattributed. That is exactly how hanzo.chat 1.0.58 shipped.
|
|
#
|
|
# `&&`, not `;`: with `;` the RUN exits with the status of the LAST command and a
|
|
# failed build would be masked. Assert on the bytes that actually ship — a key
|
|
# present in the environment and absent from the bundle is indistinguishable from
|
|
# success everywhere except the warehouse, where the traffic simply stops being
|
|
# attributable.
|
|
RUN pnpm --filter @hanzo/id-web build && \
|
|
{ grep -rqF "$VITE_PUBLISHABLE_KEY" apps/web/dist || \
|
|
{ echo "ERROR: the ingest key is not in apps/web/dist - hanzo.id would ship unattributed" >&2; exit 1; }; }
|
|
|
|
# SPA server stage — hanzoai/spa is the correct base for a Vite SPA:
|
|
# history-API fallthrough for client-side routes AND a SPA-safe CSP.
|
|
# hanzoai/static defaults to `Content-Security-Policy: default-src 'none'`
|
|
# (built for static assets, not an app that loads its own bundle), which
|
|
# blocks the SPA's own scripts and leaves a blank page. hanzoai/spa serves
|
|
# index.html for all routes with a sane CSP. Defaults: PORT=3000, ROOT=/public.
|
|
FROM ghcr.io/hanzoai/spa:1.4.11
|
|
COPY --from=build /build/apps/web/dist /public
|
|
EXPOSE 3000
|