Skip to content

Commit 4d615d6

Browse files
committed
fix(registry): use pnpm deploy for monorepo-aware Dockerfile
#100 switched the registry's `@nimblebrain/mpak-schemas` dep from `^0.2.0` (published) to `workspace:*` so schemas changes are immediately consumable without a publish-bump dance — matching the convention every other package in the workspace already uses (`cli`, `sdk-typescript`, `web`). The Dockerfile, however, was written single-package and only copied `apps/registry/`, never any workspace dep source. Build then errored across every file importing schemas: src/routes/auth.ts(3,53): error TS2307: Cannot find module '@nimblebrain/mpak-schemas' or its corresponding type declarations. ... (15 more) ## The fix: `pnpm deploy` `pnpm deploy --filter=<pkg> --prod <out>` is pnpm's purpose-built solution for "containerize a workspace package": it walks the full dependency closure (workspace + npm), builds a flattened deployable directory, and resolves all `workspace:*` links by bundling each workspace package's compiled `dist/` inline. Single COPY in the runtime stage; future workspace deps require no Dockerfile change. Builder stage: - `COPY . .` brings the whole monorepo in (kept lean by .dockerignore). - `pnpm install --frozen-lockfile` resolves all workspace links. - `pnpm --filter ... exec prisma generate` writes the generated client into the pnpm hoisted store. - `pnpm --filter @nimblebrain/mpak-registry... build` builds the registry AND every transitive workspace dep (`...` filter syntax). - `pnpm deploy --filter=@nimblebrain/mpak-registry --prod /deploy` flattens registry + prod deps + workspace closures into /deploy. Production stage: - One `COPY --from=builder /deploy ./` brings everything the runtime needs — registry dist, schemas dist, every npm prod dep. - The Prisma generated client is the one exception: its sibling `.prisma/` folder lives in the pnpm hoisted store and isn't carried by `pnpm deploy`. Two explicit COPY lines preserved from the prior Dockerfile handle that special case. `.dockerignore` (new) keeps the build context small — excludes every package's `node_modules`, `dist`, `.astro`, `.turbo`, `.git`, `.env*`, test outputs, editor state. ## Verified locally - `docker build -f apps/registry/Dockerfile -t mpak-registry:test .` → succeeds end-to-end (was failing on the original tsc step). - `docker run mpak-registry:test` → boots: Prisma initializes, every module loads, Fastify listens on :3200. - `node -e "import('@nimblebrain/mpak-schemas').then(...)"` inside the image → schemas resolves, `ServerDetailSchema` is an object. ## Why not the simpler `COPY packages/schemas/...` patch Considered first, rejected because it's per-dep — every future workspace dep added to the registry would need another COPY pair in both stages. `pnpm deploy` is what pnpm's monorepo-Docker docs recommend and scales to N workspace deps with no Dockerfile churn.
1 parent 238a13e commit 4d615d6

2 files changed

Lines changed: 85 additions & 35 deletions

File tree

.dockerignore

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Keep the docker build context small. The Dockerfile uses `COPY . .` to
2+
# pull the whole monorepo in (so `pnpm deploy` can walk the workspace
3+
# graph); this file makes sure that doesn't include build artifacts,
4+
# installed deps, or local-only state.
5+
6+
# Installed deps — pnpm reinstalls inside the image.
7+
**/node_modules
8+
.pnpm-store
9+
10+
# Build outputs — recreated by `pnpm install` + per-package build.
11+
**/dist
12+
**/build
13+
**/*.tsbuildinfo
14+
**/.astro
15+
**/.turbo
16+
**/.next
17+
18+
# VCS + workspace state.
19+
.git
20+
.github
21+
.claude
22+
23+
# Local environment + secrets — must never enter the image.
24+
.env
25+
.env.*
26+
**/.env
27+
**/.env.*
28+
29+
# Test + coverage outputs.
30+
**/coverage
31+
**/.nyc_output
32+
**/.pytest_cache
33+
**/.ruff_cache
34+
35+
# Editor / OS noise.
36+
.vscode
37+
.idea
38+
.DS_Store
39+
**/*.swp
40+
**/*.swo
41+
42+
# Misc large or sensitive paths that don't need to ship to the registry
43+
# image specifically.
44+
apps/registry/data
45+
apps/web/dist
46+
apps/web/.astro
47+
apps/docs/dist
48+
apps/docs/.astro
49+
50+
# Existing implementation-tracking dirs.
51+
.tasks

apps/registry/Dockerfile

Lines changed: 34 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,53 @@
1-
# Build stage
1+
# Build stage — full monorepo install + build the registry + flatten its
2+
# closure into /deploy via `pnpm deploy`. Workspace deps (e.g. the
3+
# `workspace:*` link to @nimblebrain/mpak-schemas) and any future
4+
# additions are handled automatically — pnpm walks the dependency
5+
# graph and copies what's needed.
26
FROM node:22-alpine AS builder
37

48
RUN corepack enable pnpm
59

610
WORKDIR /app
711

8-
# Copy workspace config
9-
COPY pnpm-workspace.yaml package.json pnpm-lock.yaml tsconfig.base.json ./
12+
# Whole monorepo lands here. `.dockerignore` keeps the context small
13+
# (no node_modules, dist, .git, etc.).
14+
COPY . .
1015

11-
# Copy the registry app
12-
COPY apps/registry/package.json apps/registry/tsconfig.json apps/registry/prisma.config.ts ./apps/registry/
13-
COPY apps/registry/prisma/ ./apps/registry/prisma/
14-
COPY apps/registry/src/ ./apps/registry/src/
15-
16-
# Install dependencies
16+
# One install resolves every workspace package's deps + workspace links.
1717
RUN pnpm install --frozen-lockfile
1818

19-
# Generate Prisma client
20-
RUN cd apps/registry && npx prisma generate
19+
# Generate the Prisma client into the pnpm hoisted store. tsc downstream
20+
# consumes its types; the runtime stage copies the generated artifacts
21+
# explicitly (see below).
22+
RUN pnpm --filter @nimblebrain/mpak-registry exec prisma generate
2123

22-
# Build
23-
RUN cd apps/registry && pnpm build
24+
# Build registry + every transitive workspace dep (schemas, etc.).
25+
RUN pnpm --filter @nimblebrain/mpak-registry... build
2426

25-
# Production stage
26-
FROM node:22-alpine AS production
27+
# Flatten registry + production deps into /deploy. pnpm walks the
28+
# dependency closure and copies every workspace package's `dist/` plus
29+
# every external prod dep — single-stage output, no per-dep COPY pairs
30+
# in the runtime stage as the workspace grows.
31+
RUN pnpm deploy --filter=@nimblebrain/mpak-registry --prod /deploy
2732

28-
RUN corepack enable pnpm
33+
# Production stage — minimal runtime image.
34+
FROM node:22-alpine AS production
2935

3036
WORKDIR /app
3137

32-
# Copy workspace config
33-
COPY pnpm-workspace.yaml package.json pnpm-lock.yaml ./
34-
35-
# Copy the registry app package.json
36-
COPY apps/registry/package.json ./apps/registry/
37-
38-
# Install production dependencies only
39-
RUN pnpm install --frozen-lockfile --prod
40-
41-
# Copy Prisma schema and generated client (Prisma 7 generates to hoisted node_modules)
42-
COPY --from=builder /app/apps/registry/prisma/ ./apps/registry/prisma/
43-
COPY --from=builder /app/node_modules/.pnpm/@prisma+client*/node_modules/@prisma/client/ ./node_modules/@prisma/client/
38+
# Everything the registry needs at runtime, prepared by `pnpm deploy`:
39+
# its own dist/, its production deps (including @prisma/client base),
40+
# and every workspace dep's compiled output.
41+
COPY --from=builder /deploy ./
42+
43+
# Prisma's generated client lands in the pnpm hoisted store at
44+
# `node_modules/.pnpm/@prisma+client*/node_modules/{.prisma,@prisma/client}/`
45+
# during `prisma generate`. `pnpm deploy` carries the @prisma/client
46+
# package itself but not the sibling .prisma folder where the
47+
# *generated* client and engine binaries live. Copy both explicitly so
48+
# the runtime can find them at the canonical lookup paths.
4449
COPY --from=builder /app/node_modules/.pnpm/@prisma+client*/node_modules/.prisma/ ./node_modules/.prisma/
45-
COPY --from=builder /app/node_modules/.pnpm/@prisma+client-runtime-utils*/node_modules/@prisma/client-runtime-utils/ ./node_modules/@prisma/client-runtime-utils/
46-
COPY --from=builder /app/node_modules/.pnpm/@prisma+client-runtime-utils*/node_modules/@prisma/client-runtime-utils/ ./node_modules/@prisma/client-runtime-utils/
47-
48-
# Copy built output
49-
COPY --from=builder /app/apps/registry/dist/ ./apps/registry/dist/
50-
51-
WORKDIR /app/apps/registry
50+
COPY --from=builder /app/node_modules/.pnpm/@prisma+client*/node_modules/@prisma/client/ ./node_modules/@prisma/client/
5251

5352
# Environment
5453
ENV NODE_ENV=production

0 commit comments

Comments
 (0)