Skip to main content

Migrations troubleshooting

"Cannot find migration folder"​

The migrator looks at process.cwd()/drizzle. Either:

  • You're running from the wrong directory.
  • The Docker image is missing COPY drizzle/.

Verify:

ls drizzle/      # should have NNNN_*.sql files and meta/_journal.json

"Drift between schema and DB"​

You hand-edited the DB, or someone ran drizzle-kit push against this DB (against the cardinal rule). Drizzle's snapshot in drizzle/meta/ no longer matches reality.

Recovery:

  1. Inspect the live schema with pg_dump --schema-only.
  2. Reconcile by editing src/db/schema/business.ts to match what's actually there.
  3. Run pnpm db:generate --name realign — this should produce an empty diff if you got the schema right.
  4. If non-empty, write a manual migration to align DB to schema, or update schema again.

Never drizzle-kit push to fix it. That's how you got here.

"Migration applied but app still uses old shape"​

Did you rebuild? pnpm build regenerates dist/. The Docker image must be rebuilt for prod deploys.

For local dev, restart pnpm start:dev — TypeScript hot-reload doesn't pick up schema files reliably.

"Generate produced an empty migration"​

Drizzle didn't detect any schema change. Reasons:

  • You edited a comment/whitespace only.
  • The snapshot in drizzle/meta/0NNN_snapshot.json already represents your "change."
  • You forgot to save the file.

Try pnpm db:generate --name <slug> --custom to force a stub migration if you really need an artifact (e.g. for a data-only migration).

"DATABASE_URL is set but migrate fails to connect"​

  • Test with psql $DATABASE_URL -c 'SELECT 1'.
  • If using TLS, DATABASE_CERT must be set OR rejectUnauthorized: false (dev only) is fine.
  • For RDS, the security group must allow inbound from your IP / Fargate task SG.
  • For local Postgres, pg_hba.conf may be set to md5 while you're trying scram-sha-256 (or vice versa).

"psql says relation does not exist"​

A migration didn't apply. Check:

SELECT name FROM __drizzle_migrations ORDER BY id;

vs. the files in drizzle/. Apply missing ones with pnpm db:migrate.

"Production migration timed out"​

Long-running ALTER TABLEs (e.g. adding a non-null column without default to a 50M row table) acquire ACCESS EXCLUSIVE locks and block writers.

Plan it:

  1. Add as nullable.
  2. Backfill in batches off the hot path.
  3. Set NOT NULL in a follow-up migration (cheap once data is in place).

Postgres 11+ supports ADD COLUMN ... NOT NULL DEFAULT <const> without rewriting the table, so for constants you may be able to do it in one shot.

"Seed fails on first deploy"​

Seed calls auth.api.signUpEmail() which writes to user, account, session. If migrations haven't run, those tables don't exist.

docker-entrypoint.sh runs migrate before seed for this reason. If you're running seed manually, run migrate first.

"ON CONFLICT clause doesn't fire"​

You're hitting a different constraint than you think. Postgres only triggers ON CONFLICT (cols) if the conflict is on that exact constraint. Use ON CONFLICT ON CONSTRAINT <name> to be explicit.