Skip to content
Latchkey

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.

Prisma
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

  1. Add directUrl to the datasource pointing at the non-pooled port.
  2. Keep the pooled connection in url for runtime queries.
  3. Run prisma migrate deploy so it uses directUrl.
schema.prisma
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.

Terminal
DATABASE_URL=postgresql://user:pass@pooler:6432/app?pgbouncer=true
DIRECT_URL=postgresql://user:pass@db:5432/app

How to prevent it

  • Always pair a pooled url with a directUrl for migrations.
  • Set pgbouncer=true on transaction-mode pooled URLs.
  • Run migrate deploy over the direct connection in CI.

Related guides

Tired of flaky CI? Latchkey auto-heals failed jobs and retries them for you. Start free →