Prisma pgbouncer pooled URL needs directUrl for migrate in CI
A PgBouncer (transaction-mode) pooled connection cannot run Prisma migrations, which need prepared statements and advisory locks. Prisma expects a directUrl for migrate while queries use the pooled url.
What this error means
Against a pooled URL, prisma migrate deploy errors with "prepared statement already exists", an advisory lock failure, or "cannot run in transaction mode", while normal queries work.
Error: ERROR: prepared statement "s0" already exists
Error: P3006 Migration failed to apply cleanly to the shadow database.Common causes
Migrations run through a transaction-mode pooler
PgBouncer in transaction mode breaks prepared statements and locks that Prisma Migrate relies on, so DDL fails.
No directUrl configured for migrations
Without directUrl, Prisma runs migrate over the pooled url and cannot get a stable direct session.
How to fix it
Set directUrl for a direct connection
- Add
directUrlto the datasource pointing at the non-pooled port. - Keep the pooled connection in
urlfor runtime queries. - Run
prisma migrate deployso it uses directUrl.
datasource db {
provider = "postgresql"
url = env("DATABASE_URL") // pooled, pgbouncer=true
directUrl = env("DIRECT_URL") // direct connection for migrate
}Mark the pooled URL as pgbouncer
Flag the pooled URL so Prisma disables prepared statements on it.
DATABASE_URL=postgresql://user:pass@pooler:6432/app?pgbouncer=true
DIRECT_URL=postgresql://user:pass@db:5432/appHow to prevent it
- Always pair a pooled url with a directUrl for migrations.
- Set
pgbouncer=trueon transaction-mode pooled URLs. - Run migrate deploy over the direct connection in CI.