Skip to content

Ship only production dependencies in the api and preview-edge images - #214

Merged
skylerberg merged 1 commit into
mainfrom
prod-only-node-modules
Sep 29, 2026
Merged

skylerberg merged 1 commit into
mainfrom
prod-only-node-modules

Conversation

@skylerberg

Copy link
Copy Markdown
Owner

Summary

Both Dockerfiles ran a full pnpm install, built, then ran pnpm install --frozen-lockfile --prod in the same directory and copied node_modules into the runtime stage. That second install removes the devDependencies' top-level links but leaves the packages themselves in node_modules/.pnpm, so typescript, eslint, vitest, knip, esbuild, kysely-codegen and their dependencies were all in the production images. The app couldn't import them, but they cost image size and pull time, and they add to what vulnerability scanners flag.

The fix: rm -rf node_modules before the prod install, plus --offline so the install relinks from the store the first install filled (and fails loudly rather than re-downloading if that store is ever missing).

Measured with the committed lockfiles and pnpm 11.21.0 (on macOS, so platform binaries differ slightly from the image):

before after (identical to a clean --prod install)
api node_modules 219 MB, 362 packages 109 MB, 180 packages
preview-edge node_modules 74 MB, 176 packages 13 MB, 89 packages

argon2 and sharp still load from the pruned api tree. tsx stays: it's a real dependency, because api/k8s/migrate-job.yaml runs the migrations through it from the image.

Also corrects an api-ci.yaml comment that listed scripts/ among the Dockerfile's inputs.

Deploy notes

No web change, so the two-commit rule doesn't apply. Merging redeploys the API (api/Dockerfile) and preview-edge. api-ci.yaml's images job builds both images on this PR, which checks the Docker build itself.

🤖 Generated with Claude Code

`pnpm install --prod` over an existing tree only unlinks devDependencies
from the top level of node_modules; the packages themselves stay in
node_modules/.pnpm and were copied into the runtime image.

- api/Dockerfile, preview-edge/Dockerfile: remove node_modules before the
  --prod install, and add --offline so it relinks from the builder's store
  instead of re-downloading. Measured with the committed lockfiles: api
  219 MB / 362 packages -> 109 MB / 180; preview-edge 74 MB / 176 -> 13 MB / 89.
- api-ci.yaml: correct a comment that listed scripts/ among the Dockerfile's
  inputs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
🟣 Preview removed (PR closed).

@skylerberg
skylerberg merged commit 7d9d326 into main Sep 29, 2026
14 checks passed
@skylerberg
skylerberg deleted the prod-only-node-modules branch September 29, 2026 01:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant