@dockerforge/core 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dockerforge/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "DockerForge engine: analyse a local project and generate production-grade Dockerfiles, .dockerignore, and Compose, and lint Dockerfiles. Offline, no network.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Docker Forge",
|
|
@@ -697,13 +697,23 @@ function generateNodeRoot(a, envVars = [], rootConfigFiles = []) {
|
|
|
697
697
|
// Build with `next build`, then run `next start` on a lean Node runtime. Serving the
|
|
698
698
|
// .next output via a generic static server would break SSR, API routes, and dynamic rendering.
|
|
699
699
|
if (a.framework === 'nextjs') {
|
|
700
|
-
const prodInstall = nodeInstallLine(a.packageManager, true, { hasScopedPackages: a.hasScopedPackages });
|
|
701
700
|
const nextSrcCopy = nextSourceCopyBlock(a, rootConfigFiles);
|
|
702
701
|
const hasPublic = Array.isArray(a.sourceDirs) && a.sourceDirs.includes('public');
|
|
703
702
|
const publicCopy = hasPublic ? `COPY --from=builder /app/public ./public\n` : '';
|
|
704
703
|
const runtimeConfigCopy = a.nextRuntimeConfig
|
|
705
704
|
? `COPY --from=builder /app/${a.nextRuntimeConfig} ./${a.nextRuntimeConfig}\n`
|
|
706
705
|
: '';
|
|
706
|
+
// Prune dev deps to production IN THE BUILDER, then copy node_modules into the runtime
|
|
707
|
+
// stage. This avoids a second install at runtime — which would re-hit the registry (and,
|
|
708
|
+
// for private/scoped packages, need the npmrc secret a second time). The builder already
|
|
709
|
+
// fetched and authenticated everything; pruning just drops the dev-only packages.
|
|
710
|
+
const secretMount = a.hasScopedPackages ? '--mount=type=secret,id=npmrc,target=/root/.npmrc ' : '';
|
|
711
|
+
const prodPrune =
|
|
712
|
+
a.packageManager === 'pnpm' ? 'RUN pnpm prune --prod'
|
|
713
|
+
: a.packageManager === 'yarn' ? `RUN ${secretMount}yarn install --frozen-lockfile --production --ignore-scripts && yarn cache clean`
|
|
714
|
+
: 'RUN npm prune --omit=dev';
|
|
715
|
+
// node images ship npm + yarn, but not pnpm — install it in the runtime stage if needed.
|
|
716
|
+
const runtimePmSetup = a.packageManager === 'pnpm' ? `RUN ${installPnpmGlobalCmd()}\n` : '';
|
|
707
717
|
|
|
708
718
|
dockerfile = `
|
|
709
719
|
# ─── Stage 1: Build ───────────────────────────────────────
|
|
@@ -718,6 +728,9 @@ ${buildInstall}
|
|
|
718
728
|
${nextSrcCopy}
|
|
719
729
|
RUN ${buildPrefix}${a.buildCommand}
|
|
720
730
|
|
|
731
|
+
# Drop dev dependencies so only production node_modules carry into the runtime image
|
|
732
|
+
${prodPrune}
|
|
733
|
+
|
|
721
734
|
# ─── Stage 2: Runtime ─────────────────────────────────────
|
|
722
735
|
FROM ${baseImage}
|
|
723
736
|
|
|
@@ -725,10 +738,10 @@ WORKDIR /app
|
|
|
725
738
|
|
|
726
739
|
${buildEnvBlock(envVars)}
|
|
727
740
|
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
741
|
+
# Next.js runs as a Node server. Copy production deps + build output from the builder —
|
|
742
|
+
# no second install here, so the (often private) registry is only contacted once, in the builder.
|
|
743
|
+
${runtimePmSetup}COPY --from=builder /app/package.json ./package.json
|
|
744
|
+
COPY --from=builder /app/node_modules ./node_modules
|
|
732
745
|
COPY --from=builder /app/${buildOut} ./${buildOut}
|
|
733
746
|
${publicCopy}${runtimeConfigCopy}${runtimeUserBlock(true)}
|
|
734
747
|
|
|
@@ -738,8 +751,11 @@ ${simpleHttpHealthcheck(a.port)}
|
|
|
738
751
|
CMD ${startCmdJson}`.trim();
|
|
739
752
|
|
|
740
753
|
improvements.push('Next.js is built and run as a Node server (next start) so SSR and API routes work — a generic static server image would break them.');
|
|
741
|
-
improvements.push('
|
|
754
|
+
improvements.push('Production node_modules are copied from the build stage (deps fetched once). For an even smaller image, set `output: "standalone"` in next.config and copy .next/standalone + .next/static.');
|
|
742
755
|
improvements.push('Only known framework config files are auto-copied — verify any project-specific build config is also COPYed (e.g. sentry.*.config.ts, next-i18next.config).');
|
|
756
|
+
if (a.hasScopedPackages) {
|
|
757
|
+
improvements.push('Scoped packages detected (may include a private registry): the install keeps a secret mount — build with `docker build --secret id=npmrc,src=.npmrc .` and a .npmrc that authenticates your scope.');
|
|
758
|
+
}
|
|
743
759
|
improvements.push('HEALTHCHECK probes /health, which Next.js does not expose by default — add a health route or point the healthcheck at an existing path.');
|
|
744
760
|
return { dockerfile, dockerignore: nodeDockerignore(), improvements };
|
|
745
761
|
}
|
package/src/engine/index.js
CHANGED
|
@@ -138,8 +138,11 @@ function toSimpleDockerfile(dockerfile) {
|
|
|
138
138
|
return dockerfile.split('\n').map(line => {
|
|
139
139
|
const isRun = /^RUN\s/.test(line);
|
|
140
140
|
if (!isRun) return line;
|
|
141
|
-
// Strip --mount=type=cache,...
|
|
142
|
-
|
|
141
|
+
// Strip only --mount=type=cache,... (a pure build-speed optimisation).
|
|
142
|
+
// KEEP --mount=type=secret,... — for private/scoped registries it's the only way the
|
|
143
|
+
// default Dockerfile can authenticate (e.g. `docker build --secret id=npmrc,src=.npmrc`).
|
|
144
|
+
// An unsupplied secret mount is a no-op, so this is safe even when no secret is passed.
|
|
145
|
+
line = line.replace(/--mount=type=cache,\S+\s+/g, '');
|
|
143
146
|
return line;
|
|
144
147
|
}).join('\n');
|
|
145
148
|
}
|