mirror of
https://github.com/hanzoai/bootnode.git
synced 2026-09-30 20:46:32 +00:00
build(web): serve the static export via hanzoai/static, not a Node server
The image build could not succeed at all. next.config.ts declares `output: "export"`, which emits ./out and never emits .next/standalone, but the Dockerfile's runner stage did: COPY --from=builder /app/.next/standalone ./ CMD ["node", "server.js"] so every build failed with `"/app/.next/standalone": not found` after compiling successfully. Two mutually exclusive deploy models in one repo; the deployed image simply stayed frozen at its last pre-`export` build. One model now — the static export, served by ghcr.io/hanzoai/static:v0.5.1 (scratch + a single Go binary; no nginx, no caddy, no Node in the runtime image), matching hanzo.ai/Dockerfile.production. Port stays 3001 via static's --port flag, so the existing contract is untouched: the bootnode-web App CR declares containerPort/servicePort 3001 and the hand-made bootnode-web-ingress routes to bootnode-web:3001 by number. Includes the deep-link fix the house template documents: the export is flat (out/pricing.html), static serves a directory's index.html, and in --spa mode a miss on extensionless /pricing falls through to the homepage — so each page also gets materialized as out/pricing/index.html. Also stops tracking web/tsconfig.tsbuildinfo, a build artifact that was dirtying the tree on every typecheck.
This commit is contained in:
@@ -108,3 +108,6 @@ HANZO-*.txt
|
||||
|
||||
# Docker artifacts
|
||||
*.tar.gz
|
||||
|
||||
# Next.js incremental build info — a build artifact, not source.
|
||||
web/tsconfig.tsbuildinfo
|
||||
|
||||
+53
-51
@@ -1,45 +1,52 @@
|
||||
# Bootnode Web Dashboard
|
||||
# Next.js 15 with React 19
|
||||
# Bootnode web console — image ghcr.io/hanzoai/bootnode:<tag>-web.
|
||||
#
|
||||
# Serves the Next.js STATIC EXPORT via ghcr.io/hanzoai/static, matching the
|
||||
# house pattern (hanzo.ai/Dockerfile.production). No nginx, no caddy, no Node
|
||||
# server in the runtime image.
|
||||
#
|
||||
# This file previously ended in a `node server.js` runner copying
|
||||
# /app/.next/standalone, but next.config.ts declares `output: "export"`, which
|
||||
# emits ./out and NEVER emits .next/standalone. The two deploy models are
|
||||
# mutually exclusive, so the image build had been failing at
|
||||
# COPY /app/.next/standalone: not found
|
||||
# and the deployed image stayed frozen at its last pre-`export` build. One
|
||||
# model now: static export, served by hanzoai/static.
|
||||
#
|
||||
# Build (BuildKit, on-cluster — no GHA). The build context is this directory,
|
||||
# so pass the subdir in the git ref:
|
||||
# --opt=context=https://github.com/hanzoai/bootnode.git#<SHA>:web
|
||||
# --opt=filename=Dockerfile
|
||||
# --output=type=image,name=ghcr.io/hanzoai/bootnode:<tag>-web,push=true
|
||||
|
||||
# Pinned digest for supply chain security - update periodically
|
||||
# Node 24 = Active LTS. Node 26 is Current, not LTS, so it is not the base for a
|
||||
# production image. Digest-pinned so the base cannot move under us the way the
|
||||
# unpinned pnpm did. pnpm 11 also requires Node >= 22.
|
||||
FROM node:24-alpine@sha256:a0b9bf06e4e6193cf7a0f58816cc935ff8c2a908f81e6f1a95432d679c54fbfd AS base
|
||||
|
||||
# Install dependencies only when needed
|
||||
FROM base AS deps
|
||||
RUN apk add --no-cache libc6-compat
|
||||
# ---- build stage: Next.js static export ----------------------------------
|
||||
# Node 24 = Active LTS (26 is Current, not LTS). Digest-pinned so the base
|
||||
# cannot drift the way the unpinned pnpm did. pnpm 11 requires Node >= 22.
|
||||
FROM node:24-alpine@sha256:a0b9bf06e4e6193cf7a0f58816cc935ff8c2a908f81e6f1a95432d679c54fbfd AS build
|
||||
WORKDIR /app
|
||||
RUN apk add --no-cache libc6-compat
|
||||
|
||||
# Install dependencies based on the preferred package manager
|
||||
# pnpm-workspace.yaml carries the pnpm settings (onlyBuiltDependencies) that
|
||||
# pnpm >= 11 no longer reads from package.json. It must land before install or
|
||||
# sharp's build script is skipped and the install fails ERR_PNPM_IGNORED_BUILDS.
|
||||
# package.json's `packageManager` field pins which pnpm corepack fetches, so the
|
||||
# build no longer changes behaviour whenever a new pnpm is published.
|
||||
# pnpm-workspace.yaml carries the pnpm settings (allowBuilds) that pnpm >= 11 no
|
||||
# longer reads from package.json, and .npmrc is auth/registry only under 11. It
|
||||
# must land before install or sharp's build script is skipped and the install
|
||||
# fails ERR_PNPM_IGNORED_BUILDS. package.json's `packageManager` pins which pnpm
|
||||
# corepack fetches, so a newly published pnpm can never change this build.
|
||||
COPY package.json pnpm-lock.yaml* pnpm-workspace.yaml ./
|
||||
RUN corepack enable pnpm && pnpm install --frozen-lockfile
|
||||
|
||||
# Rebuild the source code only when needed
|
||||
FROM base AS builder
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
# Production build-time environment variables
|
||||
# Brand is resolved from the hostname at RUNTIME against the injected brand
|
||||
# catalog (lib/brand.ts + /config.json). That is the only white-label mechanism.
|
||||
# Brand is resolved from the hostname at RUNTIME against the brand catalog
|
||||
# shipped in public/config.json (lib/brand.ts). That is the only white-label
|
||||
# mechanism.
|
||||
#
|
||||
# There are deliberately NO IAM build args. One image serves every white-label
|
||||
# host, so a build-time NEXT_PUBLIC_IAM_URL / _CLIENT_ID is one global value
|
||||
# shared by all brands — it cannot express per-host white-labeling, and because
|
||||
# NEXT_PUBLIC_* is inlined into the client bundle it silently outranks the
|
||||
# correct per-brand value with no way to fix it at deploy time. The defaults
|
||||
# that used to live here were also both wrong: iam.bootno.de does not serve TLS
|
||||
# (525) and bootnode-web is not the registered client, so any rebuild taking
|
||||
# the defaults would have broken sign-in outright. Issuer + client_id now come
|
||||
# from the brand catalog only.
|
||||
# that used to live here were also both wrong: iam.bootno.de serves no TLS (525)
|
||||
# and bootnode-web is not a registered client, so any rebuild taking the
|
||||
# defaults would have broken sign-in outright.
|
||||
ARG NEXT_PUBLIC_BRAND=bootnode
|
||||
ARG NEXT_PUBLIC_API_URL=https://api.bootno.de
|
||||
ARG NEXT_PUBLIC_WS_URL=wss://ws.bootno.de
|
||||
@@ -49,29 +56,24 @@ ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
|
||||
ENV NEXT_PUBLIC_WS_URL=$NEXT_PUBLIC_WS_URL
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
RUN corepack enable pnpm && pnpm run build
|
||||
RUN pnpm run build
|
||||
|
||||
# Production image, copy all the files and run next
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
|
||||
COPY --from=builder /app/public ./public
|
||||
|
||||
# Automatically leverage output traces to reduce image size
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
|
||||
USER nextjs
|
||||
# Clean URLs on hanzoai/static. The Next export is FLAT (out/pricing.html, …).
|
||||
# static serves a *directory's* index.html, and in --spa mode a MISS on an
|
||||
# extensionless /pricing falls through to index.html — i.e. every deep page
|
||||
# would render the homepage. Materialize a directory index per page so
|
||||
# root.Open("/pricing") finds a directory and serves the real page; the flat
|
||||
# .html files stay, so /pricing and /pricing.html both work.
|
||||
RUN cd out && for f in $(find . -type f -name '*.html' ! -name 'index.html' ! -name '404.html'); do \
|
||||
d="${f%.html}"; mkdir -p "$d"; cp "$f" "$d/index.html"; \
|
||||
done
|
||||
|
||||
# ---- serve stage: hanzoai/static (scratch + single Go binary) -------------
|
||||
# Port 3001 keeps the existing contract: the bootnode-web App CR declares
|
||||
# containerPort/servicePort 3001 and the hand-made bootnode-web-ingress routes
|
||||
# to bootnode-web:3001 by number.
|
||||
FROM ghcr.io/hanzoai/static:v0.5.1
|
||||
COPY --from=build /app/out /srv
|
||||
EXPOSE 3001
|
||||
|
||||
ENV PORT=3001
|
||||
ENV HOSTNAME="0.0.0.0"
|
||||
|
||||
CMD ["node", "server.js"]
|
||||
ENTRYPOINT ["/static"]
|
||||
CMD ["--root=/srv", "--spa", "--port=3001"]
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user