build(docker): prune devDependencies from the production and cloud images
The production stage copied the entire build stage `/app` wholesale, including every workspace member's devDependencies (typescript, vite, vitest, storybook, rolldown, ...) and unrelated workspace members' own dependencies (ui's client-side bundle deps like mermaid, lucide-react), none of which the running server touches. That alone accounted for ~1.5GB of the shipped image. Add a `pruned-app` stage, forked from `build` after all builds finish, that re-resolves node_modules with `pnpm install --prod --frozen-lockfile --filter='@paperclipai/server...'` -- server plus everything it actually depends on, transitively, production-only. `production` now copies from `pruned-app` instead of `build`. The prune has to live in its own stage rather than in `build` directly: `cloud-plugins` and `cloud-server-deps` (declared later in the file) both fork from `build` and still need its full devDependency toolchain (typescript, etc.) to compile their own TypeScript at image build time. Move `tsx` from server's devDependencies to dependencies. It looked like a dev tool, but the image's own ENTRYPOINT imports it directly (`--import ./server/node_modules/tsx/...`) to transpile the workspace packages the server consumes by TypeScript source (their `exports` field points at `./src/*.ts`, not a prebuilt `dist`) -- it's genuinely required at runtime, and a naive prod-prune would have deleted it and broken every boot. Verified locally: the `cloud` target drops from 7.5GB to 5.53GB and the `production` target lands at 5.26GB. Boot-tested the built `cloud` image: embedded Postgres initializes, all migrations apply, `/api/health` returns 200, the UI serves, `@sentry/node` resolves to the version `server/package.json` declares, and tini/orphan-reaping passes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RD53WaVFqm8pbntKZ5seWy
This commit is contained in:
parent
d2d647c34b
commit
d9d32b1f4a
30
Dockerfile
30
Dockerfile
|
|
@ -100,6 +100,34 @@ RUN pnpm --filter @paperclipai/server build
|
|||
RUN test -f server/dist/index.js || (echo "ERROR: server build output missing" && exit 1)
|
||||
RUN rm -rf packages/paperclip-runner/runner/target
|
||||
|
||||
# Prune the workspace down to @paperclipai/server's own production
|
||||
# dependency graph, in a stage of its own rather than in `build` directly:
|
||||
# `cloud-plugins` and `cloud-server-deps` below both fork from `build` and
|
||||
# need its full devDependency toolchain (typescript, etc.) still intact to
|
||||
# build their own TypeScript at image build time, so the prune can't
|
||||
# happen in `build` itself without breaking them.
|
||||
#
|
||||
# `pnpm install --frozen-lockfile` in the deps stage installed every
|
||||
# workspace member's devDependencies plus every member's own dependencies
|
||||
# -- ui's bundler/storybook toolchain, other workspace packages' test
|
||||
# tooling, none of which the running server touches -- into one hoisted
|
||||
# node_modules that `production` below would otherwise copy wholesale. The
|
||||
# builds above are already done, so re-resolving with --prod and a
|
||||
# `server...` filter (server plus everything it actually depends on,
|
||||
# transitively) is safe and drops the unused weight.
|
||||
#
|
||||
# tsx is deliberately NOT pruned: server/package.json lists it as a
|
||||
# production dependency (not dev) because the ENTRYPOINT below imports it
|
||||
# directly (`--import ./server/node_modules/tsx/...`) to transpile the
|
||||
# workspace packages the server consumes by TypeScript source -- their
|
||||
# `exports` field points at `./src/*.ts`, not a prebuilt `dist` -- so tsx
|
||||
# has to survive any devDependency prune.
|
||||
FROM build AS pruned-app
|
||||
RUN find . -maxdepth 4 -type d -name node_modules \
|
||||
-not -path '*/node_modules/*/node_modules*' -exec rm -rf {} + \
|
||||
&& pnpm install --prod --frozen-lockfile --ignore-scripts \
|
||||
--filter='@paperclipai/server...'
|
||||
|
||||
FROM base AS production
|
||||
ARG USER_UID=1000
|
||||
ARG USER_GID=1000
|
||||
|
|
@ -131,7 +159,7 @@ RUN echo "cli-tools-epoch: ${CLI_TOOLS_CACHE_EPOCH}" \
|
|||
COPY scripts/docker-entrypoint.sh /usr/local/bin/
|
||||
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
||||
|
||||
COPY --chown=node:node --from=build /app /app
|
||||
COPY --chown=node:node --from=pruned-app /app /app
|
||||
|
||||
ENV NODE_ENV=production \
|
||||
HOME=/paperclip \
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@
|
|||
"pino-pretty": "^13.1.3",
|
||||
"sharp": "^0.35.4",
|
||||
"ssh2": "^1.17.0",
|
||||
"tsx": "^4.23.12",
|
||||
"ws": "^8.21.3",
|
||||
"zod": "^4.4.3"
|
||||
},
|
||||
|
|
@ -100,7 +101,6 @@
|
|||
"@types/ws": "^8.18.1",
|
||||
"cross-env": "^10.1.0",
|
||||
"supertest": "^7.0.0",
|
||||
"tsx": "^4.23.12",
|
||||
"typescript": "^7.0.2",
|
||||
"vite": "^8.2.2",
|
||||
"vitest": "^4.1.11"
|
||||
|
|
|
|||
Loading…
Reference in New Issue