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.