build(web): standalone Docker image + drop Turbopack build cache (#4619)
The Docker production-stage opts into Next.js `output: 'standalone'` via a BUILD_STANDALONE env flag, so it ships only the traced runtime (server.js + hoisted node_modules + static/public) and runs `node server.js` instead of pnpm over the full source tree. The flag — and `outputFileTracingRoot`, which traces from the monorepo root so workspace packages are included — is set only in the Dockerfile build stage. Every other path keeps its original output: Tauri `export`, local `build-web`, dev, and the Cloudflare/OpenNext deploy (which forces standalone itself via NEXT_PRIVATE_STANDALONE). Disable the experimental `turbopackFileSystemCacheForBuild`: a build interrupted mid-compile leaves a partial cache that the next build mishandles, fanning out workers until it exhausts host RAM. Remove the pull-request CI step that cached `.next/cache` for it, now unused. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -60,18 +60,6 @@ jobs:
|
|||||||
node-version: 24
|
node-version: 24
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
|
|
||||||
# Turbopack persists its build cache here (turbopackFileSystemCacheForBuild).
|
|
||||||
# The key is deterministic (no commit SHA) so every PR restores the same
|
|
||||||
# entry — in practice the one `main` last saved, which is the only cache
|
|
||||||
# sibling PRs can all see.
|
|
||||||
- name: cache Turbopack build cache
|
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
|
||||||
with:
|
|
||||||
path: apps/readest-app/.next/cache
|
|
||||||
key: turbo-build-web-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
|
|
||||||
restore-keys: |
|
|
||||||
turbo-build-web-${{ runner.os }}-
|
|
||||||
|
|
||||||
- name: install Dependencies
|
- name: install Dependencies
|
||||||
working-directory: apps/readest-app
|
working-directory: apps/readest-app
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
+19
-7
@@ -45,15 +45,27 @@ COPY --from=dependencies /app/apps/readest-app/public/vendor /app/apps/readest-a
|
|||||||
COPY --from=dependencies /app/packages/foliate-js/node_modules /app/packages/foliate-js/node_modules
|
COPY --from=dependencies /app/packages/foliate-js/node_modules /app/packages/foliate-js/node_modules
|
||||||
COPY . .
|
COPY . .
|
||||||
WORKDIR /app/apps/readest-app
|
WORKDIR /app/apps/readest-app
|
||||||
|
# Opt into the self-contained `.next/standalone` tree for this image only;
|
||||||
|
# next.config.mjs gates `output: 'standalone'` on BUILD_STANDALONE so other
|
||||||
|
# web builds keep their default output.
|
||||||
|
ENV BUILD_STANDALONE=true
|
||||||
RUN pnpm build-web
|
RUN pnpm build-web
|
||||||
|
|
||||||
|
# Production runtime ships only the standalone server, its traced node_modules,
|
||||||
|
# and the static/public assets — no pnpm, no source tree, no dev dependencies,
|
||||||
|
# no build cache. `output: 'standalone'` (next.config.mjs) emits the self-contained
|
||||||
|
# tree under .next/standalone, so the entrypoint is a plain `node server.js`.
|
||||||
FROM docker.io/library/node:24-slim@sha256:24dc26ef1e3c3690f27ebc4136c9c186c3133b25563ae4d7f0692e4d1fe5db0e AS production-stage
|
FROM docker.io/library/node:24-slim@sha256:24dc26ef1e3c3690f27ebc4136c9c186c3133b25563ae4d7f0692e4d1fe5db0e AS production-stage
|
||||||
ENV PNPM_HOME="/pnpm"
|
ENV NODE_ENV=production
|
||||||
ENV PATH="$PNPM_HOME:$PATH"
|
ENV PORT=3000
|
||||||
RUN corepack enable
|
ENV HOSTNAME=0.0.0.0
|
||||||
RUN corepack prepare pnpm@11.1.1 --activate
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=build /app /app
|
# Monorepo-rooted standalone tree: server.js + hoisted, traced node_modules.
|
||||||
WORKDIR /app/apps/readest-app
|
COPY --from=build --chown=node:node /app/apps/readest-app/.next/standalone ./
|
||||||
ENTRYPOINT ["pnpm", "start-web", "-H", "0.0.0.0"]
|
# Static and public assets are not part of the standalone trace; copy them next
|
||||||
|
# to the server so their default relative paths resolve.
|
||||||
|
COPY --from=build --chown=node:node /app/apps/readest-app/.next/static ./apps/readest-app/.next/static
|
||||||
|
COPY --from=build --chown=node:node /app/apps/readest-app/public ./apps/readest-app/public
|
||||||
|
USER node
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
ENTRYPOINT ["node", "apps/readest-app/server.js"]
|
||||||
|
|||||||
@@ -14,12 +14,23 @@ if (isDev) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const exportOutput = appPlatform !== 'web' && !isDev;
|
const exportOutput = appPlatform !== 'web' && !isDev;
|
||||||
|
// Opt-in standalone output, set only by the Docker production build
|
||||||
|
// (Dockerfile). Every other path keeps the original behavior: Tauri `export`,
|
||||||
|
// local `build-web` (output undefined), dev, and the Cloudflare/OpenNext
|
||||||
|
// deploy — which forces standalone itself via NEXT_PRIVATE_STANDALONE.
|
||||||
|
const standaloneOutput = !exportOutput && process.env['BUILD_STANDALONE'] === 'true';
|
||||||
|
|
||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
// Ensure Next.js uses SSG instead of SSR
|
// Ensure Next.js uses SSG instead of SSR
|
||||||
// https://nextjs.org/docs/pages/building-your-application/deploying/static-exports
|
// https://nextjs.org/docs/pages/building-your-application/deploying/static-exports
|
||||||
output: exportOutput ? 'export' : undefined,
|
// The Docker production image opts into a self-contained `.next/standalone`
|
||||||
|
// tree (see Dockerfile) so it can ship only the traced runtime; all other
|
||||||
|
// web builds fall back to the default server output.
|
||||||
|
output: exportOutput ? 'export' : standaloneOutput ? 'standalone' : undefined,
|
||||||
|
// Monorepo: trace from the repo root so workspace packages land in the
|
||||||
|
// standalone tree. Only relevant to — and only set for — the Docker build.
|
||||||
|
outputFileTracingRoot: standaloneOutput ? path.join(__dirname, '../../') : undefined,
|
||||||
pageExtensions: exportOutput ? ['jsx', 'tsx'] : ['js', 'jsx', 'ts', 'tsx'],
|
pageExtensions: exportOutput ? ['jsx', 'tsx'] : ['js', 'jsx', 'ts', 'tsx'],
|
||||||
// Note: This feature is required to use the Next.js Image component in SSG mode.
|
// Note: This feature is required to use the Next.js Image component in SSG mode.
|
||||||
// See https://nextjs.org/docs/messages/export-image-api for different workarounds.
|
// See https://nextjs.org/docs/messages/export-image-api for different workarounds.
|
||||||
@@ -28,11 +39,11 @@ const nextConfig = {
|
|||||||
},
|
},
|
||||||
devIndicators: false,
|
devIndicators: false,
|
||||||
experimental: {
|
experimental: {
|
||||||
// Persist Turbopack's compilation cache to `.next/` so CI can restore it
|
// Dev caching is on by default since Next 16.1. We deliberately do NOT
|
||||||
// between runs. Dev caching is on by default since Next 16.1; build
|
// enable Turbopack's build cache (turbopackFileSystemCacheForBuild, beta):
|
||||||
// caching is opt-in (beta).
|
// a build interrupted mid-compile leaves a partial cache that the next
|
||||||
|
// build mishandles, fanning out workers until it exhausts RAM.
|
||||||
turbopackFileSystemCacheForDev: true,
|
turbopackFileSystemCacheForDev: true,
|
||||||
turbopackFileSystemCacheForBuild: true,
|
|
||||||
},
|
},
|
||||||
// Configure assetPrefix or else the server won't properly resolve your assets.
|
// Configure assetPrefix or else the server won't properly resolve your assets.
|
||||||
assetPrefix: '',
|
assetPrefix: '',
|
||||||
|
|||||||
Reference in New Issue
Block a user