Deploying Prisma

Prisma Intermediate/Advanced

Chapter 9 ยท Deploying Prisma

Everything so far has run on a laptop. Deploying a Prisma app raises a few specific questions: when the client gets generated, how the running app and the migration step find the database, how many connections each environment opens, and what changes on serverless and edge platforms. This chapter works through them, then takes an honest look at Prisma's own hosted products.

Environments and Their Settings

EnvironmentDatabaseMigrations run by
DevelopmentLocal (often Docker)You, with migrate dev
Test / CIThrowaway (Chapter 5)CI, with migrate deploy
StagingProduction-like copyThe deploy pipeline
ProductionReal dataThe deploy pipeline, once per release (Chapter 8)

Keep connection strings in environment variables set by the platform, never in the repository. Commit a .env.example listing the variable names with placeholder values, and add .env to .gitignore.

Two connection strings

In Prisma 7 the running app and the CLI connect separately: the app through its driver adapter, the CLI through the datasource.url in the config file. That makes it natural to give each its own URL — useful as soon as a connection pooler sits in front of the database, because migrations need a direct connection:

# .env.example DATABASE_URL="postgresql://app:***@pooler.example.com:6432/blog" # app traffic, via the pooler DIRECT_URL="postgresql://app:***@db.example.com:5432/blog" # migrations, straight to the database
// prisma config file: used by the CLI (migrate deploy, db pull...) datasource: { url: env("DIRECT_URL") }, // lib/prisma.ts: used by the running app const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });

Building: Generate the Client Explicitly

Since Prisma Fundamentals, the client has been generated into your own folder (generated/prisma). That folder is build output: add it to .gitignore and generate it during every build. Don't rely on it appearing as a side effect of installing packages or running migrations — in Prisma 7, migrate commands don't generate it for you.

// package.json "scripts": { "build": "prisma generate && tsc", "release": "prisma migrate deploy", "start": "node dist/server.js" }
No more engine binaries
Older Prisma versions shipped a Rust "query engine" binary that had to match the server's operating system, leading to binaryTargets settings and "engine not found" errors in Docker. Prisma 7's prisma-client generator is written in TypeScript and talks to the database through your driver adapter, so that whole class of deployment problem is gone.

A Docker image

# Dockerfile FROM node:22-slim AS build WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build # prisma generate + tsc FROM node:22-slim WORKDIR /app COPY package*.json ./ RUN npm ci --omit=dev COPY --from=build /app/dist ./dist COPY --from=build /app/generated ./generated COPY prisma ./prisma COPY prisma.config.ts ./ CMD ["npm", "start"]

The migrations folder and config are copied so the same image can run the release step. Note that npm ci --omit=dev skips dev dependencies: if prisma (the CLI) is a dev dependency, run migrations from the build image or a separate job instead. Adjust prisma.config.ts if your project uses the prisma7.config.ts name.

Shutting down cleanly

const server = app.listen(port); process.on("SIGTERM", () => { server.close(async () => { // stop taking requests, finish current ones await prisma.$disconnect(); // then release database connections process.exit(0); }); });

Platforms send SIGTERM before stopping a container. Disconnecting once, at shutdown, is good practice; disconnecting after each request is not — Prisma's docs warn it slows every request down.

Long-Running Servers vs. Serverless

Long-running serverServerless functions
Client instancesOne per process, for its whole lifeOne per function instance; many can exist at once
Where to create the clientAt startup (lib/prisma.ts)Outside the handler, so warm instances reuse it
Pool sizeChapter 7's budgetStart at 1 per instance without an external pooler
Main riskToo many processes × pool sizeA traffic spike creates hundreds of instances and exhausts the database
Usual fixTune pool sizesAn external pooler (PgBouncer or a managed equivalent)
// A serverless handler import { PrismaClient } from "../generated/prisma/client"; import { PrismaPg } from "@prisma/adapter-pg"; // Created once per instance, reused while the instance stays warm const prisma = new PrismaClient({ adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL, max: 1 }), }); export async function handler() { const posts = await prisma.post.findMany({ where: { published: true }, take: 10 }); return { statusCode: 200, body: JSON.stringify(posts) }; }
The development hot-reload problem
Frameworks that reload code on every save (Next.js in development, for example) can create a new PrismaClient on each reload and leak connections. Prisma's documented fix is to keep the client on globalThis in development, since global variables survive reloads.

Edge Runtimes

Edge platforms such as Cloudflare Workers and Vercel Edge Functions run JavaScript close to users, but they aren't Node.js and often can't open ordinary TCP database connections. What works depends on the driver:

DriverHow it connectsWorks on
Neon serverless, PlanetScale serverlessHTTPCloudflare Workers and Vercel Edge
libSQL / TursoHTTPCloudflare Workers and Vercel Edge
Cloudflare D1Cloudflare bindingCloudflare Workers only
node-postgres (pg)TCP via Cloudflare's connect()Cloudflare Workers only, not Vercel Edge

Because Prisma 7 always uses a driver adapter, running at the edge is mostly a matter of choosing an edge-compatible driver and its adapter. Check your platform's and Prisma's current docs before committing: this area changes often.

The edge isn't automatically faster
A function running next to the user but talking to a database on another continent pays that distance on every query. For a database-heavy page, running near the database is usually faster. Edge makes most sense for work that needs little or no database access, or with a globally distributed or cached data layer.

Prisma's Hosted Products: an Honest Look

Prisma (the company) sells hosted services alongside the free, open-source ORM. You don't need any of them to use Prisma ORM.

ProductWhat it isConsider it whenTrade-offs
Prisma PostgresA managed PostgreSQL databaseYou want a database with pooling built in and little setupCompare price, region and backup options with other managed Postgres providers
Prisma AccelerateA hosted connection pool with optional query cachingServerless or edge apps exhausting connections; read-heavy pagesAn extra network hop and dependency; cached data can be stale

Weigh them the way you would any vendor: what problem it solves for your app, what it costs at your traffic, and how hard it would be to leave. A self-managed PgBouncer, or your cloud provider's own pooler, solves the connection problem without tying you to one vendor. Product names, features and pricing change — check Prisma's current documentation rather than relying on this summary.

Hands-On Exercises

Exercise 1

Set up the blog API's configuration for two URLs (pooled and direct), a .env.example, .gitignore entries and build/release/start scripts. Explain which process uses which URL.

๐Ÿ“„ View solution
Exercise 2

A serverless version of the API works in testing but fails with "too many connections" during a traffic spike. Diagnose it and list the fixes in order of preference.

๐Ÿ“„ View solution
Exercise 3

Write a short decision note choosing where to deploy the blog API (a single container, serverless, or edge) for a site with readers mostly in one country and a PostgreSQL database in the same region. Justify it using this chapter.

๐Ÿ“„ View solution

Chapter 9 Quick Reference

  • Secrets in environment variables; commit .env.example, ignore .env and generated/
  • App uses the adapter's URL (pooled); CLI uses the config's datasource.url (direct)
  • Build runs prisma generate; release runs migrate deploy once; start runs the app
  • Prisma 7: no engine binaries, no binaryTargets headaches
  • $disconnect() once on SIGTERM, never per request
  • Serverless: client outside the handler, pool of 1, external pooler for spikes
  • Edge: pick an edge-compatible driver + adapter; mind the distance to the database
  • Hosted products (Prisma Postgres, Accelerate) are optional; compare them like any vendor