Files
console/Dockerfile.embed
hanzo-devandHanzo Dev fc5fe3c616 build: move console to pnpm, one lockfile, one pinned package manager
npm could never run `npm ci` here. The Dockerfile said so in a comment: @hanzo/gui
pulls a react-native tree whose platform and optional packages resolve differently
between npm versions, so a lockfile written by one npm failed under another, and
the build ran `npm install` — resolving the tree fresh every time and using the
committed lockfile as a suggestion. A lockfile nobody installs from is decoration,
which is how it drifted far enough that `npm ci` was already dead on main before
this change (react-native-worklets missing from the tree it claimed to describe).

pnpm records every platform in the lockfile, so the build installs exactly what is
committed and fails loudly rather than quietly resolving something else. Both
Dockerfiles now `corepack enable && pnpm install --frozen-lockfile`, corepack takes
the version from `packageManager`, and package-lock.json is gone. .gitignore is
reversed accordingly: pnpm-lock.yaml is the tracked one and every other manager's
lockfile is ignored, so a stray `npm install` cannot leave a second source of truth.

pnpm-workspace.yaml carries the two settings this needs, both documented in place.
Install scripts are denied unless named, and esbuild and sharp are named because the
app does not build without them. The release-age gate is excluded by SCOPE for
@hanzo/* rather than by version, because pnpm rewrites a per-version entry on every
bump, and a file that rewrites itself during install makes --frozen-lockfile fail in
CI — the exact determinism this change exists to get.

Verified on pnpm 11.17.0: frozen-lockfile install clean, tsc 0 errors, 3056 tests
passing, `pnpm build` compiles, and `pnpm build:embed` emits the real bundle cloud
fail-hards on (367KB index.html, 5.5M _next). node_modules stays self-contained —
every symlink is relative into node_modules/.pnpm — so the runner stage's
COPY --from=build of node_modules still resolves.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-27 08:13:46 -07:00

31 lines
1.8 KiB
Docker

# hanzoai/console — the EMBED artifact.
#
# Builds the console SPA static export (`pnpm build:embed` → out/) ONCE, as a
# versioned immutable image whose rootfs is just the bundle at /dist. hanzoai/cloud
# then does `FROM registry.hanzo.ai/hanzoai/console-embed:<ver> AS console` +
# `COPY --from=console /dist/ webui/dist/` instead of re-running the install+Next export on
# EVERY cloud release (the ~15-min cache-busted long pole). Console changes far less
# often than cloud ships, so this moves the build to console's own cadence and turns
# a cloud rebuild into a registry pull.
#
# The Next.js SERVER image (standalone/admin hosts) stays in build-image.yml — this
# is a separate, additional artifact, not a replacement.
FROM public.ecr.aws/docker/library/node:24-alpine AS build
RUN apk add --no-cache git
WORKDIR /console
# Heap headroom so the full @hanzo/gui static export never OOMs into a stub; telemetry off.
ENV NEXT_TELEMETRY_DISABLED=1 NODE_OPTIONS=--max-old-space-size=8192
# Bake the console.hanzo.ai analytics property (public per-site id) — the SAME default
# cloud baked at build:embed time, so the embedded console keeps tracking identically.
# GA4/Pixel stay unset. Public id, not a KMS secret.
ARG NEXT_PUBLIC_ANALYTICS_WEBSITE_ID=7dce54ee-41f6-4751-96bf-fe005067c7c7
ENV NEXT_PUBLIC_ANALYTICS_WEBSITE_ID=$NEXT_PUBLIC_ANALYTICS_WEBSITE_ID
COPY . .
RUN corepack enable && pnpm install --frozen-lockfile
# FAIL-HARD: the export MUST emit a real bundle (non-empty out/index.html + out/_next/),
# never a placeholder shell — same invariant cloud's console stage enforced.
RUN pnpm build:embed && [ -s out/index.html ] && [ -d out/_next ] \
&& echo ">> embedded REAL console bundle: $(wc -c < out/index.html)-byte index.html, $(du -sh out/_next | cut -f1) _next/"
FROM scratch
COPY --from=build /console/out/ /dist/