Migrations
ArchVault uses Drizzle ORM for database schema management. Migrations are generated from TypeScript schema definitions — you never write SQL by hand.
How It Works
Section titled “How It Works”Schema files (TypeScript) → drizzle-kit generate → SQL migration files → drizzle-kit migrate → Database- You define tables in
apps/web/src/lib/schema/*.tsusing Drizzle’s TypeScript API drizzle-kit generatediffs the schema against existing migrations and produces new SQLdrizzle-kit migrate(dev) ormigrate.mjs(production) applies pending SQL to the database
Development Workflow
Section titled “Development Workflow”-
Edit the schema in
apps/web/src/lib/schema/:apps/web/src/lib/schema/example.ts import { pgTable, text, timestamp } from "drizzle-orm/pg-core";export const example = pgTable("example", {id: text("id").primaryKey(),name: text("name").notNull(),createdAt: timestamp("created_at").defaultNow().notNull(),}); -
Generate the migration:
Terminal window pnpm db:generateThis creates a new timestamped folder in
apps/web/drizzle/containingmigration.sql. -
Review the generated SQL — verify it matches your intent:
Terminal window cat apps/web/drizzle/$(ls -t apps/web/drizzle | head -1)/migration.sql -
Apply the migration:
Terminal window pnpm db:migrate -
Commit both the schema changes and the generated migration files to git.
Running Migrations
Section titled “Running Migrations”Uses drizzle-kit (a dev dependency):
# Generate migration from schema changespnpm db:generate
# Apply pending migrationspnpm db:migrateBoth commands use the config in apps/web/drizzle.config.ts and read DATABASE_URL from apps/web/.env.
Production containers use migrate.mjs — a standalone script that does not require drizzle-kit:
# Runs automatically on container start when AUTO_MIGRATE=true (default)# To run manually:docker compose -f compose.prod.yaml exec app node migrate.mjsAuto-migration
Section titled “Auto-migration”By default, the Docker entrypoint runs all pending migrations before starting the app:
if [ "${AUTO_MIGRATE:-true}" = "true" ]; then echo "Running database migrations..." node migrate.mjsfiexec node .output/server/index.mjsTo disable auto-migration, set AUTO_MIGRATE=false in your .env. This is useful if you prefer to run migrations manually or in a separate CI step.
Migration Files
Section titled “Migration Files”Migrations live in apps/web/drizzle/. Each migration is a timestamped folder:
apps/web/drizzle/├── 20260314122457_powerful_silverclaw/│ └── migration.sql├── 20260314142206_bitter_thunderbolt/│ └── migration.sql├── ...└── meta/ ├── _journal.json ← tracks migration order and applied status └── *.json ← snapshot of schema at each migrationThe meta/_journal.json file tracks which migrations have been applied. Drizzle uses this to determine which migrations are pending.
Troubleshooting
Section titled “Troubleshooting”Migration failed mid-way
Section titled “Migration failed mid-way”If a migration fails partway through (e.g. a constraint violation), the database may be in a partially migrated state:
- Check what was applied by looking at the error output
- Fix the issue in your schema
- Generate a new corrective migration with
pnpm db:generate - Apply it with
pnpm db:migrate
Resetting the dev database
Section titled “Resetting the dev database”To start fresh during development:
# Stop and remove the database volumepnpm docker:compose:down:v
# Restart the databasepnpm docker:compose:up
# Re-apply all migrationspnpm db:migrateSchema drift
Section titled “Schema drift”If your database gets out of sync with the migrations (e.g. manual DDL changes), the safest fix is to reset the dev database (above). In production, always use migrations — never run DDL directly.
”No config path” error
Section titled “”No config path” error”Ensure you’re running pnpm db:generate or pnpm db:migrate from the repo root, not from apps/web/. The Turborepo scripts handle the working directory.