2023-01-24 17:10:25 -07:00
|
|
|
##### DEPENDENCIES
|
|
|
|
|
|
2026-05-03 20:30:36 -05:00
|
|
|
FROM node:20-alpine3.20 AS deps
|
2023-02-21 09:53:06 -07:00
|
|
|
RUN apk add --no-cache libc6-compat
|
2023-01-24 17:10:25 -07:00
|
|
|
WORKDIR /app
|
|
|
|
|
|
2026-01-22 09:20:36 -07:00
|
|
|
# Enable corepack for pnpm
|
|
|
|
|
RUN corepack enable && corepack prepare pnpm@10.28.1 --activate
|
|
|
|
|
|
2026-04-03 11:28:33 -06:00
|
|
|
# Copy Prisma schema for client generation (postinstall generates schema.prisma from this)
|
|
|
|
|
COPY prisma/schema.full.prisma ./prisma/
|
2026-04-03 11:17:26 -06:00
|
|
|
|
|
|
|
|
# Install dependencies — lockfile and scripts rarely change, so they go first.
|
|
|
|
|
# package.json changes on every version bump but pnpm only needs it for the
|
|
|
|
|
# workspace root name; the store cache mount lets pnpm reuse downloaded packages
|
|
|
|
|
# even when this layer is invalidated.
|
|
|
|
|
COPY pnpm-lock.yaml package.json ./
|
2024-11-20 14:44:02 -04:00
|
|
|
COPY scripts ./scripts
|
2026-04-28 14:12:07 -06:00
|
|
|
COPY patches ./patches
|
2024-11-20 14:44:02 -04:00
|
|
|
|
2026-04-03 11:17:26 -06:00
|
|
|
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
|
|
|
|
|
pnpm install --frozen-lockfile
|
2023-01-24 17:10:25 -07:00
|
|
|
|
|
|
|
|
##### BUILDER
|
|
|
|
|
|
2026-05-03 20:30:36 -05:00
|
|
|
FROM node:20-alpine3.20 AS builder
|
2023-01-24 17:10:25 -07:00
|
|
|
ARG NEXT_PUBLIC_IMAGE_LOCATION
|
|
|
|
|
ARG NEXT_PUBLIC_CONTENT_DECTECTION_LOCATION
|
|
|
|
|
ARG NEXT_PUBLIC_MAINTENANCE_MODE
|
|
|
|
|
WORKDIR /app
|
2026-01-22 09:20:36 -07:00
|
|
|
|
|
|
|
|
# Enable corepack for pnpm
|
|
|
|
|
RUN corepack enable && corepack prepare pnpm@10.28.1 --activate
|
|
|
|
|
|
2023-01-24 17:10:25 -07:00
|
|
|
COPY --from=deps /app/node_modules ./node_modules
|
|
|
|
|
COPY . .
|
2025-11-20 10:47:51 -07:00
|
|
|
# Restore generated schema.prisma from deps (COPY . . overwrites it with source which doesn't have it)
|
|
|
|
|
COPY --from=deps /app/prisma/schema.prisma ./prisma/schema.prisma
|
2023-01-24 17:10:25 -07:00
|
|
|
|
2025-09-04 12:20:30 -05:00
|
|
|
ENV NEXT_TELEMETRY_DISABLED=1
|
2023-01-24 17:10:25 -07:00
|
|
|
|
2026-06-04 14:54:56 -05:00
|
|
|
# Node heap for the Next.js build. Default raised 6144 -> 8192: a cold build
|
|
|
|
|
# (no warm .next/cache) peaks higher than an incremental one and OOMs at 6 GB on
|
|
|
|
|
# newer commits. Build-arg so a builder with more memory can raise it further.
|
|
|
|
|
ARG NODE_BUILD_MEM=8192
|
2026-04-02 13:45:20 -05:00
|
|
|
RUN --mount=type=cache,target=/app/.next/cache \
|
2026-06-04 14:54:56 -05:00
|
|
|
SKIP_ENV_VALIDATION=1 IS_BUILD=true NODE_OPTIONS="--max_old_space_size=${NODE_BUILD_MEM}" pnpm run build
|
2023-01-24 17:10:25 -07:00
|
|
|
|
ci(bundle): report-only First Load JS budget in the Dockerfile build (Tekton) (#2511)
* ci(bundle): add report-only size-limit bundle budget job
Next 16 removed per-route build stats, leaving no bundle-size regression
signal. Adds a `bundle-budget` job to pr-check.yml that builds the app
(SKIP_ENV_VALIDATION, no secrets) and runs size-limit over the shared
client chunks (framework/main/webpack/_app + a coarse total) defined in
.size-limit.json.
Report-only for now: continue-on-error + intentionally loose limits. This
is also the first GH Actions job to run a full `next build` (~8GB heap vs
~7GB standard runner) so early runs probe feasibility. Once a baseline is
observed: tighten limits to baseline+headroom, drop continue-on-error,
and make "Bundle Budget" a required check to gate. If Build OOMs, move to
a larger runner label.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Potential fix for pull request finding 'CodeQL / Workflow does not contain permissions'
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
* ci(bundle): run size-limit in the Dockerfile build, not GH Actions
Switch the bundle-size check from a separate GH Actions job (which would
duplicate the full ~8GB next build) to a stage in the Dockerfile builder,
right after `pnpm run build` where .next already exists. The Tekton
buildkit build (preview + prod) now reports the size-limit numbers with
no extra build — consistent with where app builds live.
Report-only during the soak via `|| true` (numbers print to the build
log). To gate later: drop `|| true` so a bundle regression fails the
image build. Reverts the pr-check.yml bundle-budget job; keeps
.size-limit.json + the size-limit deps + the `size` script.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* ci(bundle): fix size-limit globs for Turbopack output
The live preview build (next 16.2.7 Turbopack) revealed the webpack-era
globs match nothing — Turbopack emits opaque hashed chunks
(0--619vzepha0.js, turbopack-*.js), no framework-/main-/webpack-/_app-
files. Those 4 entries errored ("can't find files"); only the recursive
total worked.
Baseline from the build: total client JS = 38.29 MB brotli (3615 chunks).
Drop the 4 broken named-chunk entries; keep the working total with a
42 MB limit (~10% headroom). Still report-only (|| true in Dockerfile).
Note: the coarse total is a weak regression signal under Turbopack's
heavy code-splitting; a per-page First Load JS budget needs parsing
.next/build-manifest.json (follow-up).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* ci(bundle): manifest-based First Load JS budget (replaces size-limit)
size-limit's globs can't see Turbopack's opaque hashed chunks, so it could
only report a coarse 38 MB total (weak signal). Replace it with
scripts/bundle-budget.mjs, which parses .next/build-manifest.json to
reconstruct the metric Next used to print:
First Load JS(route) = brotli(union(pages[route], pages["/_app"], polyfills))
shared-by-all-pages = brotli(pages["/_app"] + polyfills)
Reports shared + total + the heaviest routes, checks .bundle-budget.json
(report-only; `--gate` exits non-zero on a breach). No deps (Node stdlib
zlib/fs), no extra build — still runs in the Dockerfile builder stage.
Removes size-limit + @size-limit/file + .size-limit.json.
Budgets are loose placeholders; tighten to baseline+headroom from the
first build's printed First Load JS numbers, then add --gate + drop the
`|| true` to enforce.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* ci(bundle): tighten First Load JS budgets to baseline + headroom
From build pr-preview-2511-fzrjd: shared-by-all = 425.9 kB, heaviest route
(/user/[username]/models) = 1.13 MB. Set shared 470 kB (~10%) and routeMax
1.3 MB (~15%) so the report-only check is meaningful instead of passing
trivially at the 1 MB/3 MB placeholders. Still report-only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* ci(bundle): bake bundle-budget report into the image for PR surfacing
Write the size report to /app/bundle-budget.txt (still report-only, still
printed to the build log) and COPY it into the runner image. A new Tekton
bundle-comment task surfaces it on the PR via `kubectl exec ... cat` — no
duplicate build. Uses redirect+cat instead of `| tee` so the script's exit
code is preserved for the future --gate flip.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: retrigger preview build (bundle-comment task now live)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: retrigger preview (bundle-comment rollout-race fix live)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: retrigger preview (pr-deployer exec RBAC now granted)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* ci(bundle): drop pnpm preamble from the bundle report
Invoke `node scripts/bundle-budget.mjs` directly instead of `pnpm run size`
so pnpm's lifecycle echo (`> model-share@… size /app`) stays out of
/app/bundle-budget.txt and the PR comment. The `size` script stays in
package.json for local use.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: retrigger preview (collapsible bundle comment)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2026-06-13 15:26:47 -05:00
|
|
|
# Bundle-size budget (report-only during the soak). Next 16 (Turbopack) emits
|
|
|
|
|
# opaque hashed chunks and removed per-route build stats, so scripts/bundle-budget.mjs
|
|
|
|
|
# parses .next/build-manifest.json to reconstruct per-page First Load JS (brotli)
|
|
|
|
|
# + a shared-by-all-pages figure. Runs here because .next exists in this stage
|
|
|
|
|
# and the build already happened — no duplicate build. `|| true` keeps it
|
|
|
|
|
# report-only (numbers print to the build log); to GATE, add `--gate` to the
|
|
|
|
|
# node invocation and replace `|| true; cat ...` with `; rc=$?; cat ...; exit $rc`
|
|
|
|
|
# so a budget breach fails the image build.
|
|
|
|
|
# The report is also written to /app/bundle-budget.txt and COPYied into the
|
|
|
|
|
# runner image so the Tekton bundle-comment task can surface it on the PR
|
|
|
|
|
# (kubectl exec ... cat) without a duplicate build.
|
|
|
|
|
# Invoke node directly (not `pnpm run size`) so pnpm's lifecycle preamble
|
|
|
|
|
# (`> model-share@… size /app`) stays out of the report/comment.
|
|
|
|
|
RUN node scripts/bundle-budget.mjs > /app/bundle-budget.txt 2>&1 || true; cat /app/bundle-budget.txt
|
|
|
|
|
|
feat(build): ship server source maps for prod CPU-profile de-minification (#2460)
* feat(build): ship server source maps for prod CPU-profile de-minification
Prod pods capture V8 .cpuprofiles to find event-loop blockers, but the
standalone image shipped zero server .js.map files, so frames were minified
and unnameable (e.g. `p @ src_17njnbr._.js:0`).
Build change (Turbopack / Next 16):
- Under Turbopack the only source-map lever is `turbopackSourceMaps`, whose
build-time default IS `productionBrowserSourceMaps` (already true). So server
chunk maps (.next/server/**/*.js.map) are already emitted at build time;
`experimental.serverSourceMaps` is webpack-only and ignored by Turbopack.
Reworded next.config.mjs comment to state this accurately.
- output:'standalone' traces via @vercel/nft, which follows import/require/fs
and does NOT copy sibling .map files, so the maps were dropped from the image.
Dockerfile now stages just the server-chunk maps (structure-preserving tar)
in the builder and overlays them onto .next/server in the runner.
Maps are inert at runtime (loaded only by an inspector/stack resolver) -> no
request-path perf cost. Cost is build time + ~order-of-tens-of-MB image size.
Resolver tool:
- scripts/resolve-cpuprofile.mjs maps each frame's (chunk.js, line, col) back to
original {source, line, name} via the `source-map` package, ranks hottest
self-time leaves, and reconstructs the longest-synchronous-block stack, named.
- Verified end-to-end against a real esbuild-minified bundle + map (synthetic
profile frames resolved back to original fn names + .ts locations). Prod proof
awaits the next map-enabled deploy + a fresh capture.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(build): publish server maps as on-demand artifact, not in runtime image
The previous approach overlaid all server .js.map files into the runtime
image, adding ~761 MB to every prod pod (11,305 maps) for a debug aid that
is only needed OFFLINE by the cpuprofile resolver.
Instead:
- Dockerfile: revert the runtime-stage map COPY (runtime image is lean again).
Add a `FROM scratch AS maps` target holding ONLY the staged server maps; it
shares every builder layer (cache hit) and is published separately.
- resolve-cpuprofile.mjs: add `--image <tag-or-ref>` mode that fetches that
build's maps from ghcr.io/civitai/civitai-web-maps:<tag> via `crane export`
(falls back to `oras pull`), then resolves. Keeps the local `--maps <dir>`
mode. Maps are keyed by the exact image tag so a profile from image X
resolves against X's maps.
The Tekton build pipeline (datapacket-talos) publishes the `maps` target to
the sibling repo after the main build+push, reusing the same ghcr creds, as a
non-fatal step.
Verified end-to-end: pushed a synthetic maps image to a local registry, then
`--image` fetched + extracted it and de-minified frames to original src/*.ts
functions.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(source-maps): correct stale next.config comment + clean up resolver temp dir on error
Audit follow-ups on the artifact-based source-maps PR:
- next.config.mjs: the comment still described the pre-revision behavior (maps
baked into the runtime image). Reworded to reflect the on-demand maps artifact.
- resolve-cpuprofile.mjs: wrap the post-fetch body in try/finally so the fetched
maps temp dir (hundreds of MB) is always cleaned up, even if resolution throws
(previously leaked on the --image error path).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 08:55:04 -05:00
|
|
|
# Server source maps (.next/server/**/*.js.map) are emitted by the build
|
|
|
|
|
# (productionBrowserSourceMaps -> turbopackSourceMaps) but @vercel/nft does NOT
|
|
|
|
|
# trace sibling .map files into .next/standalone, so they never reach runtime.
|
|
|
|
|
# Collect ONLY the server-chunk maps into a structure-preserving staging dir.
|
|
|
|
|
# These are NOT shipped in the runtime image (they added ~761 MB to every prod
|
|
|
|
|
# pod — too much for a debug aid). Instead they are published as a separate,
|
|
|
|
|
# fetched-on-demand `maps` artifact image (see the `maps` target below + the
|
|
|
|
|
# Tekton maps-publish step), keyed by the same tag as the runtime image, so a
|
|
|
|
|
# `.cpuprofile` captured from image X can be de-minified offline against X's maps.
|
|
|
|
|
# Build-chunk map filenames are content hashes (no spaces/newlines), so the
|
|
|
|
|
# newline-delimited `tar -T -` files-from list is safe and works under both GNU tar
|
|
|
|
|
# and busybox tar (alpine). `tar | tar` preserves the dir structure
|
|
|
|
|
# (e.g. chunks/<hash>.js.map) so each map keeps its .next/server-relative path.
|
|
|
|
|
# (Comments must stay OUTSIDE the RUN: Docker collapses the \-continuations into one
|
|
|
|
|
# line, where an inline `#` would swallow the rest of the command.)
|
|
|
|
|
RUN mkdir -p /app/server-maps && \
|
|
|
|
|
cd /app/.next/server && \
|
|
|
|
|
{ find . -name '*.js.map' | tar -cf - -T - | tar -xf - -C /app/server-maps || true; } && \
|
|
|
|
|
echo "Staged $(find /app/server-maps -name '*.js.map' | wc -l) server source maps ($(du -sh /app/server-maps | cut -f1))"
|
|
|
|
|
|
|
|
|
|
##### MAPS ARTIFACT (fetched on-demand; NOT part of the runtime image)
|
|
|
|
|
#
|
|
|
|
|
# A minimal `FROM scratch` image holding ONLY the staged server source maps,
|
|
|
|
|
# under /server-maps mirroring the .next/server tree. Published to a sibling
|
|
|
|
|
# registry repo (ghcr.io/civitai/civitai-web-maps:<same-tag>) by the Tekton
|
|
|
|
|
# maps-publish step using the SAME ghcr credentials as the runtime push — no new
|
|
|
|
|
# secrets. It shares every builder layer with the runtime build, so building this
|
|
|
|
|
# target is a buildkit cache hit plus one small layer; it is never pulled by a
|
|
|
|
|
# running pod. The cpuprofile resolver fetches it on demand keyed by image tag
|
|
|
|
|
# (scripts/resolve-cpuprofile.mjs --image ...).
|
|
|
|
|
FROM scratch AS maps
|
|
|
|
|
COPY --from=builder /app/server-maps/ /server-maps/
|
|
|
|
|
|
2023-01-24 17:10:25 -07:00
|
|
|
##### RUNNER
|
|
|
|
|
|
2026-05-03 20:30:36 -05:00
|
|
|
FROM node:20-alpine3.20 AS runner
|
2023-01-24 17:10:25 -07:00
|
|
|
WORKDIR /app
|
|
|
|
|
|
2025-09-04 12:20:30 -05:00
|
|
|
ENV NODE_ENV=production
|
2023-01-24 17:10:25 -07:00
|
|
|
|
|
|
|
|
# ENV NEXT_TELEMETRY_DISABLED 1
|
|
|
|
|
|
|
|
|
|
RUN addgroup --system --gid 1001 nodejs
|
|
|
|
|
RUN adduser --system --uid 1001 nextjs
|
|
|
|
|
|
|
|
|
|
COPY --from=builder /app/next.config.mjs ./
|
|
|
|
|
COPY --from=builder /app/public ./public
|
|
|
|
|
COPY --from=builder /app/package.json ./package.json
|
ci(bundle): report-only First Load JS budget in the Dockerfile build (Tekton) (#2511)
* ci(bundle): add report-only size-limit bundle budget job
Next 16 removed per-route build stats, leaving no bundle-size regression
signal. Adds a `bundle-budget` job to pr-check.yml that builds the app
(SKIP_ENV_VALIDATION, no secrets) and runs size-limit over the shared
client chunks (framework/main/webpack/_app + a coarse total) defined in
.size-limit.json.
Report-only for now: continue-on-error + intentionally loose limits. This
is also the first GH Actions job to run a full `next build` (~8GB heap vs
~7GB standard runner) so early runs probe feasibility. Once a baseline is
observed: tighten limits to baseline+headroom, drop continue-on-error,
and make "Bundle Budget" a required check to gate. If Build OOMs, move to
a larger runner label.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Potential fix for pull request finding 'CodeQL / Workflow does not contain permissions'
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
* ci(bundle): run size-limit in the Dockerfile build, not GH Actions
Switch the bundle-size check from a separate GH Actions job (which would
duplicate the full ~8GB next build) to a stage in the Dockerfile builder,
right after `pnpm run build` where .next already exists. The Tekton
buildkit build (preview + prod) now reports the size-limit numbers with
no extra build — consistent with where app builds live.
Report-only during the soak via `|| true` (numbers print to the build
log). To gate later: drop `|| true` so a bundle regression fails the
image build. Reverts the pr-check.yml bundle-budget job; keeps
.size-limit.json + the size-limit deps + the `size` script.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* ci(bundle): fix size-limit globs for Turbopack output
The live preview build (next 16.2.7 Turbopack) revealed the webpack-era
globs match nothing — Turbopack emits opaque hashed chunks
(0--619vzepha0.js, turbopack-*.js), no framework-/main-/webpack-/_app-
files. Those 4 entries errored ("can't find files"); only the recursive
total worked.
Baseline from the build: total client JS = 38.29 MB brotli (3615 chunks).
Drop the 4 broken named-chunk entries; keep the working total with a
42 MB limit (~10% headroom). Still report-only (|| true in Dockerfile).
Note: the coarse total is a weak regression signal under Turbopack's
heavy code-splitting; a per-page First Load JS budget needs parsing
.next/build-manifest.json (follow-up).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* ci(bundle): manifest-based First Load JS budget (replaces size-limit)
size-limit's globs can't see Turbopack's opaque hashed chunks, so it could
only report a coarse 38 MB total (weak signal). Replace it with
scripts/bundle-budget.mjs, which parses .next/build-manifest.json to
reconstruct the metric Next used to print:
First Load JS(route) = brotli(union(pages[route], pages["/_app"], polyfills))
shared-by-all-pages = brotli(pages["/_app"] + polyfills)
Reports shared + total + the heaviest routes, checks .bundle-budget.json
(report-only; `--gate` exits non-zero on a breach). No deps (Node stdlib
zlib/fs), no extra build — still runs in the Dockerfile builder stage.
Removes size-limit + @size-limit/file + .size-limit.json.
Budgets are loose placeholders; tighten to baseline+headroom from the
first build's printed First Load JS numbers, then add --gate + drop the
`|| true` to enforce.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* ci(bundle): tighten First Load JS budgets to baseline + headroom
From build pr-preview-2511-fzrjd: shared-by-all = 425.9 kB, heaviest route
(/user/[username]/models) = 1.13 MB. Set shared 470 kB (~10%) and routeMax
1.3 MB (~15%) so the report-only check is meaningful instead of passing
trivially at the 1 MB/3 MB placeholders. Still report-only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* ci(bundle): bake bundle-budget report into the image for PR surfacing
Write the size report to /app/bundle-budget.txt (still report-only, still
printed to the build log) and COPY it into the runner image. A new Tekton
bundle-comment task surfaces it on the PR via `kubectl exec ... cat` — no
duplicate build. Uses redirect+cat instead of `| tee` so the script's exit
code is preserved for the future --gate flip.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: retrigger preview build (bundle-comment task now live)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: retrigger preview (bundle-comment rollout-race fix live)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: retrigger preview (pr-deployer exec RBAC now granted)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* ci(bundle): drop pnpm preamble from the bundle report
Invoke `node scripts/bundle-budget.mjs` directly instead of `pnpm run size`
so pnpm's lifecycle echo (`> model-share@… size /app`) stays out of
/app/bundle-budget.txt and the PR comment. The `size` script stays in
package.json for local use.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: retrigger preview (collapsible bundle comment)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2026-06-13 15:26:47 -05:00
|
|
|
# Bundle-budget report (report-only) — surfaced on the PR by the Tekton
|
|
|
|
|
# bundle-comment task via `kubectl exec ... cat /app/bundle-budget.txt`.
|
|
|
|
|
COPY --from=builder /app/bundle-budget.txt ./bundle-budget.txt
|
2023-01-24 17:10:25 -07:00
|
|
|
|
|
|
|
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
|
|
|
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
feat(build): ship server source maps for prod CPU-profile de-minification (#2460)
* feat(build): ship server source maps for prod CPU-profile de-minification
Prod pods capture V8 .cpuprofiles to find event-loop blockers, but the
standalone image shipped zero server .js.map files, so frames were minified
and unnameable (e.g. `p @ src_17njnbr._.js:0`).
Build change (Turbopack / Next 16):
- Under Turbopack the only source-map lever is `turbopackSourceMaps`, whose
build-time default IS `productionBrowserSourceMaps` (already true). So server
chunk maps (.next/server/**/*.js.map) are already emitted at build time;
`experimental.serverSourceMaps` is webpack-only and ignored by Turbopack.
Reworded next.config.mjs comment to state this accurately.
- output:'standalone' traces via @vercel/nft, which follows import/require/fs
and does NOT copy sibling .map files, so the maps were dropped from the image.
Dockerfile now stages just the server-chunk maps (structure-preserving tar)
in the builder and overlays them onto .next/server in the runner.
Maps are inert at runtime (loaded only by an inspector/stack resolver) -> no
request-path perf cost. Cost is build time + ~order-of-tens-of-MB image size.
Resolver tool:
- scripts/resolve-cpuprofile.mjs maps each frame's (chunk.js, line, col) back to
original {source, line, name} via the `source-map` package, ranks hottest
self-time leaves, and reconstructs the longest-synchronous-block stack, named.
- Verified end-to-end against a real esbuild-minified bundle + map (synthetic
profile frames resolved back to original fn names + .ts locations). Prod proof
awaits the next map-enabled deploy + a fresh capture.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(build): publish server maps as on-demand artifact, not in runtime image
The previous approach overlaid all server .js.map files into the runtime
image, adding ~761 MB to every prod pod (11,305 maps) for a debug aid that
is only needed OFFLINE by the cpuprofile resolver.
Instead:
- Dockerfile: revert the runtime-stage map COPY (runtime image is lean again).
Add a `FROM scratch AS maps` target holding ONLY the staged server maps; it
shares every builder layer (cache hit) and is published separately.
- resolve-cpuprofile.mjs: add `--image <tag-or-ref>` mode that fetches that
build's maps from ghcr.io/civitai/civitai-web-maps:<tag> via `crane export`
(falls back to `oras pull`), then resolves. Keeps the local `--maps <dir>`
mode. Maps are keyed by the exact image tag so a profile from image X
resolves against X's maps.
The Tekton build pipeline (datapacket-talos) publishes the `maps` target to
the sibling repo after the main build+push, reusing the same ghcr creds, as a
non-fatal step.
Verified end-to-end: pushed a synthetic maps image to a local registry, then
`--image` fetched + extracted it and de-minified frames to original src/*.ts
functions.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(source-maps): correct stale next.config comment + clean up resolver temp dir on error
Audit follow-ups on the artifact-based source-maps PR:
- next.config.mjs: the comment still described the pre-revision behavior (maps
baked into the runtime image). Reworded to reflect the on-demand maps artifact.
- resolve-cpuprofile.mjs: wrap the post-fetch body in try/finally so the fetched
maps temp dir (hundreds of MB) is always cleaned up, even if resolution throws
(previously leaked on the --image error path).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 08:55:04 -05:00
|
|
|
# NOTE: server source maps are intentionally NOT copied into the runtime image.
|
|
|
|
|
# They are published as the separate `maps` target above (fetched on-demand by
|
|
|
|
|
# the cpuprofile resolver), keeping the prod pod lean (~761 MB smaller).
|
2023-01-24 17:10:25 -07:00
|
|
|
|
|
|
|
|
USER nextjs
|
|
|
|
|
EXPOSE 3000
|
2025-09-04 12:20:30 -05:00
|
|
|
ENV PORT=3000
|
|
|
|
|
ENV NEXT_TELEMETRY_DISABLED=1
|
2023-01-24 17:10:25 -07:00
|
|
|
|
2024-05-23 14:31:49 -05:00
|
|
|
CMD ["node", "--", "server.js", "--", "--expose-gc"]
|