From f4bcad91b0c9554e802d00beb67019aea3225813 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Guti=C3=A9rrez?= <35082514+alezmad@users.noreply.github.com> Date: Sun, 5 Apr 2026 15:37:21 +0100 Subject: [PATCH] refactor(deploy): trim docker images via pnpm deploy --legacy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use pnpm deploy to flatten each package's runtime subset into /deploy, then copy ONLY that into the runtime stage. Catalog + workspace:* specifiers previously forced full-workspace resolution into every image's node_modules — unnecessary for either runtime. Results (arm64, same smoke tests pass): - broker: 3.26GB → 341MB (-90%, drops all devDeps incl. drizzle-kit) - migrate: 3.27GB → 653MB (-80%, keeps drizzle-kit which IS runtime) Broker /health confirms GIT_SHA build-arg still propagates (gitSha: "30bc24f" in smoke test). Migrate still reads drizzle.config.ts and attempts the connection correctly. --legacy flag needed because pnpm 10 defaults to inject-workspace- packages mode which the monorepo doesn't opt into; legacy is safe here. --ignore-scripts on deploy skips the root postinstall (sherif lint:ws) which has nothing to do with runtime. Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/broker/Dockerfile | 20 +++++++++----------- packages/db/Dockerfile | 19 +++++++------------ 2 files changed, 16 insertions(+), 23 deletions(-) diff --git a/apps/broker/Dockerfile b/apps/broker/Dockerfile index 0d67c56..189342b 100644 --- a/apps/broker/Dockerfile +++ b/apps/broker/Dockerfile @@ -15,10 +15,14 @@ RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certifi # Copy full workspace (pnpm needs lockfile + all package.jsons to resolve workspace:* and catalog:) COPY . . -# Install all workspace deps (broker needs @turbostarter/db + @turbostarter/shared and their transitive deps) -RUN pnpm install --frozen-lockfile --ignore-scripts +# Install all workspace deps, then flatten broker's prod subset into /deploy. +# pnpm deploy: resolves workspace:* to real copies, drops catalog: references, +# drops devDependencies (--prod), produces a self-contained runtime directory +# with only what this one package + its transitive prod deps need. +RUN pnpm install --frozen-lockfile --ignore-scripts && \ + pnpm deploy --legacy --prod --ignore-scripts --filter=@claudemesh/broker /deploy -# Stage 2: minimal Bun runtime +# Stage 2: minimal Bun runtime — copy only the flat /deploy subset FROM oven/bun:1.2-slim AS runtime WORKDIR /app @@ -29,13 +33,7 @@ ENV GIT_SHA=$GIT_SHA ENV NODE_ENV=production ENV BROKER_PORT=7900 -# Copy workspace root metadata + node_modules + only the packages the broker needs -COPY --from=deps --chown=bun:bun /app/package.json /app/pnpm-workspace.yaml /app/pnpm-lock.yaml /app/.npmrc ./ -COPY --from=deps --chown=bun:bun /app/node_modules ./node_modules -COPY --from=deps --chown=bun:bun /app/apps/broker ./apps/broker -COPY --from=deps --chown=bun:bun /app/packages/db ./packages/db -COPY --from=deps --chown=bun:bun /app/packages/shared ./packages/shared -COPY --from=deps --chown=bun:bun /app/tooling/typescript ./tooling/typescript +COPY --from=deps --chown=bun:bun /deploy /app EXPOSE 7900 @@ -44,4 +42,4 @@ HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=3 \ # Non-root user (oven/bun image ships with 'bun' uid 1000) USER bun -CMD ["bun", "apps/broker/src/index.ts"] +CMD ["bun", "src/index.ts"] diff --git a/packages/db/Dockerfile b/packages/db/Dockerfile index 150864b..16e4953 100644 --- a/packages/db/Dockerfile +++ b/packages/db/Dockerfile @@ -5,7 +5,7 @@ # # Build from repo root: docker build -f packages/db/Dockerfile -t claudemesh-migrate . -# Stage 1: resolve pnpm workspace + install deps (Bun base + standalone pnpm) +# Stage 1: resolve pnpm workspace + flatten db's subset to /deploy via pnpm deploy FROM oven/bun:1.2 AS deps WORKDIR /app @@ -17,25 +17,20 @@ RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certifi # pnpm needs full workspace context to resolve workspace:* and catalog: specifiers COPY . . -# drizzle-kit is a devDependency → install the full dep graph (NOT --prod). -# Filter to db + its transitive deps only → ~20x smaller install than the whole workspace. -RUN pnpm install --frozen-lockfile --ignore-scripts --filter "@turbostarter/db..." +# Install full workspace, then flatten the db package's deploy subset. +# Keeps devDependencies (--no `--prod`) because drizzle-kit IS the runtime here. +RUN pnpm install --frozen-lockfile --ignore-scripts && \ + pnpm deploy --legacy --ignore-scripts --filter=@turbostarter/db /deploy -# Stage 2: minimal Bun runtime (executes drizzle-kit CLI + TS config) +# Stage 2: minimal Bun runtime — copy only /deploy FROM oven/bun:1.2-slim AS runtime WORKDIR /app ENV NODE_ENV=production -# Copy workspace metadata, the db package (schema + migrations + config), shared (transitive) -COPY --from=deps --chown=bun:bun /app/package.json /app/pnpm-workspace.yaml /app/pnpm-lock.yaml /app/.npmrc ./ -COPY --from=deps --chown=bun:bun /app/node_modules ./node_modules -COPY --from=deps --chown=bun:bun /app/packages/db ./packages/db -COPY --from=deps --chown=bun:bun /app/packages/shared ./packages/shared -COPY --from=deps --chown=bun:bun /app/tooling/typescript ./tooling/typescript +COPY --from=deps --chown=bun:bun /deploy /app USER bun -WORKDIR /app/packages/db # drizzle-kit reads DATABASE_URL from env via ./src/env.ts, runs pending migrations, # exits 0 on success / non-zero on failure. No long-running process.