Prisma 7 in Docker Production Images
Problem
Prisma 7 changed how datasource URLs are configured and significantly expanded the CLI's runtime dependency closure. Two things that worked in Prisma 5/6 break silently in Prisma 7:
url = env("DATABASE_URL")inschema.prismais no longer valid — Prisma 7 moves this toprisma.config.tsviadefineConfig().- Selectively copying only
node_modules/prisma+node_modules/@prismais not enough — Prisma 7 CLI loads additional packages at runtime that aren't in those scopes.
Additionally, Yarn 4 node-modules linker creates .bin/prisma as a symlink. Docker COPY
dereferences symlinks, silently converting the symlink to a regular file at the wrong location,
breaking WASM engine loading.
Context / Trigger Conditions
- Prisma 7+ with a multi-stage Dockerfile
- Yarn 4 with
nodeLinker: node-modules - Container crashes at startup with any of:
PrismaConfigEnvError: Cannot resolve environment variable: DATABASE_URLError: Cannot find module '@prisma/config'Error: Cannot find module 'effect'Error: WASM file not foundor similar engine loading errors
prisma validatepasses locally but fails in the container
Root Causes
Root Cause 1: schema.prisma no longer holds the datasource URL
In Prisma 7, datasource db { url = env("DATABASE_URL") } was removed from schema.prisma.
The URL now lives in prisma.config.ts:
// packages/server/prisma.config.ts
import { defineConfig, env } from 'prisma/config'
export default defineConfig({
schema: 'prisma/schema.prisma',
migrations: { path: 'prisma/migrations' },
datasource: { url: env('DATABASE_URL') },
})
Root Cause 2: Prisma 7 CLI has a large non-bundled dependency closure
Prisma 7 CLI requires at runtime (beyond prisma and @prisma):
effectfast-checkpure-rand@prisma/config- Other transitive deps
Selective copying of node_modules/prisma + node_modules/@prisma misses these.
Root Cause 3: Docker COPY dereferences Yarn 4 .bin/ symlinks
In Yarn 4 node-modules linker, .bin/prisma is a symlink to
../prisma/build/index.js (relative path). Docker COPY follows the symlink and
copies the file content to the destination, preserving __dirname as the source
directory — not the destination. This breaks WASM loading which is relative to __dirname.
Solution
Step 1: Copy prisma.config.ts from the build stage
COPY --from=build-server /app/packages/server/prisma ./prisma
COPY --from=build-server /app/packages/server/prisma.config.ts ./prisma.config.ts
Step 2: Copy full node_modules (until a deps-prod stage is available)
# Prisma 7 CLI requires its full transitive dependency closure at runtime.
# TODO: Introduce a deps-prod stage (yarn workspaces focus server --production
# after adding @yarnpkg/plugin-workspace-tools) to reduce image size.
COPY --from=deps /app/node_modules ./node_modules
Step 3: Recreate the .bin/prisma symlink after COPY
# Yarn 4 .bin/prisma is a symlink that Docker COPY dereferences. Recreate it so
# __dirname resolves to prisma/build/ for correct WASM engine loading.
RUN ln -sf /app/node_modules/prisma/build/index.js /app/node_modules/.bin/prisma
Step 4: Add node_modules/.bin to PATH and update CMD
ENV PATH="/app/node_modules/.bin:$PATH"
CMD ["sh", "-c", "prisma migrate deploy && node server/index.mjs"]
Step 5: Move prisma from devDependencies to dependencies
In packages/server/package.json, move prisma to dependencies so the runtime
requirement is explicit and won't silently break if the install strategy changes:
{
"dependencies": {
"prisma": "7.x.x"
}
}
Complete Production Stage Example
# ── Production ───────────────────────────────────────────────────
FROM node:22-alpine AS production
WORKDIR /app
COPY --from=build-server /app/packages/server/dist ./server
COPY --from=build-web /app/packages/web/dist ./web
# Prisma: schema + migrations + config (Prisma 7 uses prisma.config.ts for datasource URL)
COPY --from=build-server /app/packages/server/prisma ./prisma
COPY --from=build-server /app/packages/server/prisma.config.ts ./prisma.config.ts
# Prisma 7 CLI requires its full transitive dependency closure at runtime.
# TODO: Introduce a deps-prod stage (yarn workspaces focus server --production
# after adding @yarnpkg/plugin-workspace-tools) to reduce image size.
COPY --from=deps /app/node_modules ./node_modules
# Yarn 4 .bin/prisma is a symlink that Docker COPY dereferences. Recreate it so
# __dirname resolves to prisma/build/ for correct WASM engine loading.
RUN ln -sf /app/node_modules/prisma/build/index.js /app/node_modules/.bin/prisma
ENV PATH="/app/node_modules/.bin:$PATH"
ENV NODE_ENV=production
EXPOSE 4000
CMD ["sh", "-c", "prisma migrate deploy && node server/index.mjs"]
Verification
Run the production image without DATABASE_URL — it should fail with a clear
Prisma config error (not a Node.js module error):
docker build -t test:local .
docker run --rm test:local
# Expected: PrismaConfigEnvError: Cannot resolve environment variable: DATABASE_URL
# NOT: Error: Cannot find module '...'
If you see Cannot find module, the dependency closure is still incomplete.
Future Optimization (Image Size)
The full node_modules copy significantly inflates the image. The correct long-term fix:
- Add
@yarnpkg/plugin-workspace-toolsto Yarn plugins - Add a
deps-prodstage:RUN yarn workspaces focus server --production - Copy only Prisma-required packages from that stage
Until then, the full copy is the reliable approach.
Notes
- Prisma 7's
.tsconfig files are executed by the CLI's internal TypeScript loader —tsxis NOT required in the production image - The
restart: unless-stoppedDocker compose policy will cause a crash-loop ifprisma migrate deployfails — set astart_periodon your healthcheck and monitor logs prismashould be independencies, notdevDependencies, since it runs at container startup