Skip to content

Migrations & Seeding

Migration workflow

bash
cd packages/database

# 1. edit prisma/schema.prisma
# 2. create and apply the migration
pnpm db:migrate                    # prisma migrate dev — prompts for a name

# 3. rebuild so consuming services see the new types
cd ../.. && pnpm build --filter=@playpals/db

prisma migrate dev does three things: diffs the schema against the database, writes SQL into prisma/migrations/<timestamp>_<name>/migration.sql, applies it, and regenerates the client. Commit the generated migration directory.

The commands

CommandRunsWhen
pnpm db:generateprisma generateAfter pulling a schema change without a new migration
pnpm db:migrateprisma migrate devLocal development — creates and applies
pnpm db:deployprisma migrate deployProduction — applies existing migrations, never creates
pnpm db:pushprisma db pushThrowaway local experiments only — no migration file
pnpm db:seedprisma/seed.tsPopulate demo data

Never run db:push against a shared database

It mutates the schema without recording a migration, so the migration history no longer describes reality and the next migrate deploy fails or silently diverges. It is fine on your own laptop for a quick experiment you intend to throw away.

Migration rules

Never edit an applied migration. Once a migration has run anywhere other than your laptop it is immutable — Prisma records a checksum, and changing the file makes migrate deploy fail. Fix mistakes with a new migration.

Plan destructive changes in two steps. Renaming a column or dropping a table in a single migration means downtime, because the old code is still running while the new schema is live. Use expand/contract:

  1. Expand — add the new column, deploy code that writes both and reads the new one.
  2. Contract — a later migration drops the old column, once nothing reads it.

Watch for locks. Adding a column with a non-null default rewrites the table on older Postgres. Adding an index locks writes unless you use CREATE INDEX CONCURRENTLY, which Prisma will not generate — write it by hand in the migration SQL when the table is large.

Applying migrations in production

The API image does not run migrations at startup. Apply them deliberately:

bash
kubectl run prisma-migrate --rm -it \
  --image=registry.digitalocean.com/playpalzproduction/api:latest \
  --env="DATABASE_URL=$DATABASE_URL" \
  --restart=Never \
  -- npx prisma migrate deploy --schema packages/database/prisma/schema.prisma

Order matters: apply an additive migration before deploying the code that needs it; apply a destructive migration after the code that stopped using the old shape is fully rolled out.

Always take a snapshot of the managed database first. See Runbooks.

Checking state

bash
cd packages/database
pnpm exec prisma migrate status     # applied vs pending
pnpm exec prisma validate           # schema is valid
pnpm exec prisma format             # canonical formatting

Prisma configuration

packages/database/prisma.config.ts supplies the datasource URL and the seed command:

ts
export default defineConfig({
  datasource: { url: env("DATABASE_URL") },
  migrations: {
    seed: "ts-node --project ./prisma/tsconfig.seed.json ./prisma/seed.ts",
  },
});

schema.prisma itself declares only provider = "postgresql" — no url. The connection comes from this config for CLI operations, and from the driver adapter in src/client.ts at runtime.

Seeding

bash
cd packages/database
pnpm db:seed

The seed produces a full demo environment rather than a handful of rows — users of each persona, posts of every type, channels, DMs, sessions, subscriptions, shop items, wallets and ledger entries, notifications, trending, and sponsors. Enough that every screen in the app has something to render.

What it does with media

Avatars and post images are fetched from pravatar and picsum, processed with Sharp, and uploaded to Spaces. Clip videos from assets/ are uploaded to Mux. The upload helpers fall back to source URLs when credentials are missing, so seeding works without Spaces or Mux access — you get remote URLs instead of your own CDN.

Safety properties

  • Additive. Seeded accounts live in a dedicated @playpalz.gg email space so they cannot collide with existing rows.
  • Idempotent. Re-running aborts early if the seed data is already present.
  • Games are not seeded. It queries the existing Game rows (populated by igdb-heartbeat) and links to them.

Seeded credentials

Every seeded account uses the password Password123!. Local demo data only — never reuse it anywhere real.

FilePurpose
prisma/seed.tsThe active seed
prisma/seed/env.tsEnv loading for the seed
prisma/seed/spaces.tsImage processing and upload
prisma/seed/mux.tsVideo upload
prisma/seedStore.tsStore products
prisma/seedOLD.tsSuperseded — do not use

Resetting locally

bash
docker compose down -v            # deletes the volume and all local data
docker compose up -d db redis
cd packages/database
pnpm db:migrate
pnpm db:seed

Prisma also offers pnpm exec prisma migrate reset, which drops, re-migrates, and re-seeds in one step. Both are local-only operations.

Internal documentation — PlayPalz platform