perf(build): resolve pnpm from the pin and reuse the real pnpm store
CI / check (push) Successful in 29s
CI / tests-integration (push) Successful in 2m13s
CI / tests-unit (push) Successful in 2m28s
CI / tests-imaging-browsers (push) Successful in 2m29s
CI / tests-quality (push) Successful in 55s
CI / tests-ui (push) Successful in 5m10s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 3m47s
CI / check (push) Successful in 29s
CI / tests-integration (push) Successful in 2m13s
CI / tests-unit (push) Successful in 2m28s
CI / tests-imaging-browsers (push) Successful in 2m29s
CI / tests-quality (push) Successful in 55s
CI / tests-ui (push) Successful in 5m10s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 3m47s
The Docker build was slower than it needed to be and could drift away from the lockfile. Three separate causes, fixed together. The image installed a pinned [email protected].1 while package.json pins 12.11.2 through `packageManager`. pnpm silently self-managed the difference at build time: it downloaded the right version into an uncached location and never verified the sha512 hash. Install corepack instead and let it resolve version and hash from the pin, so the image can no longer diverge and a tampered tarball is rejected. The resolved pnpm lives in COREPACK_HOME inside the image, so the migrations image still runs pnpm offline (`docker run --entrypoint pnpm ... db:migrate`). The pnpm store cache-mount pointed at /root/.local/share/pnpm/store, but pnpm keeps its store in $PNPM_HOME/store/v11. The mount was a no-op: every build re-downloaded all 493 packages ("reused 0, downloaded 493"). Mounting the store where pnpm actually keeps it turns that into "reused 493, downloaded 0". The build context carried backups/ (232MB of rotating database dumps) and public/vendor/ (regenerated by `pnpm assets:editor` from node_modules). Both grew the context and invalidated the `COPY . .` layer on every daily backup rotation, forcing a full Next.js rebuild. Excluding them takes the context from 276MB to 23MB. Compose now also exports the build cache to .docker-cache/cms and imports it again, so the cache survives `scripts/docker-prune.sh`, which trims the daemon cache to 4GB after every deploy. Verified by pruning the entire daemon cache: the next build restored every layer from the local export and downloaded nothing. Also: byparr's floating `:latest` tag is now pulled on every start, the migrations build in scripts/docker-update.sh imports the same local cache, the README no longer claims the build needs host networking or an iptables-disabled daemon, the pnpm rationale in renovate.json reflects that Docker follows the pin automatically, and contract tests pin the new invariants so the store path cannot drift back.
This commit is contained in:
1 parent
1e81cac504
commit
8ca538f99f
8 files changed
+141
-8
No files matched your search
@@ -87,3 +87,53 @@ it("does not prerender installation metadata into a shared image", () => {
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("resolves pnpm from the packageManager pin instead of a hardcoded version", () => {
|
||||
// Een vastgepind pnpm-pakket in de Dockerfile loopt altijd uit sync met de
|
||||
// packageManager-pin in package.json (12.10.1 vs 12.11.2).
|
||||
expect(dockerfile).not.toMatch(/npm install -g pnpm@/);
|
||||
expect(dockerfile).toContain("corepack enable pnpm");
|
||||
expect(dockerfile).toContain("corepack install");
|
||||
|
||||
// De gedownloade pnpm moet in de image staan, anders kan de runtime
|
||||
// (`docker run --entrypoint pnpm <migrations-image> db:migrate`) pnpm niet
|
||||
// gebruiken. Een cache-mount verdwijnt na de build.
|
||||
const corepackHome = dockerfile.match(/ENV COREPACK_HOME="([^"]+)"/)?.[1];
|
||||
expect(corepackHome).toBeTruthy();
|
||||
expect(dockerfile).not.toContain(`--mount=type=cache,target=${corepackHome}`);
|
||||
});
|
||||
|
||||
it("caches the pnpm store on the path pnpm actually uses", () => {
|
||||
const pnpmHome = dockerfile.match(/ENV PNPM_HOME="([^"]+)"/)?.[1];
|
||||
expect(pnpmHome).toBeTruthy();
|
||||
const cacheMounts = [
|
||||
...dockerfile.matchAll(/--mount=type=cache,target=([^\s\\]+)/g),
|
||||
].map((match) => match[1]);
|
||||
const storeMounts = cacheMounts.filter((target) => target.includes("pnpm"));
|
||||
expect(storeMounts.length).toBeGreaterThan(0);
|
||||
// Met een ander pad (bv. /root/.local/share/pnpm/store) cache't de mount
|
||||
// niets en downloadt elke build alle packages opnieuw.
|
||||
for (const target of storeMounts) {
|
||||
expect(target).toBe(`${pnpmHome}/store`);
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps volatile and regenerated files out of the build context", () => {
|
||||
const ignored = readFileSync(".dockerignore", "utf8");
|
||||
// `backups/` (dagelijkse rotatie, ~232MB) maakte elke buildcontext groot
|
||||
// en invalideerde bij elke rotatie de `COPY . .`-laag.
|
||||
expect(ignored).toMatch(/^backups\/?$/m);
|
||||
// public/vendor/tinymce wordt tijdens de build opnieuw gegenereerd door
|
||||
// `pnpm assets:editor` en staat niet in git.
|
||||
expect(ignored).toMatch(/^public\/vendor\/?$/m);
|
||||
});
|
||||
|
||||
it("keeps a local build cache that survives the daemon cache prune", () => {
|
||||
const compose = readFileSync("docker-compose.yml", "utf8");
|
||||
expect(compose).toContain("cache_from:");
|
||||
expect(compose).toContain("cache_to:");
|
||||
expect(compose).toContain("type=local");
|
||||
// De cache-map mag niet in de buildcontext of in git terechtkomen.
|
||||
expect(readFileSync(".dockerignore", "utf8")).toMatch(/^\.docker-cache\/?$/m);
|
||||
expect(readFileSync(".gitignore", "utf8")).toMatch(/^\.docker-cache\/?$/m);
|
||||
});
|
||||
Reference in new issue
Block a user