Skip to content

Database

OpenCauldron uses PostgreSQL as its only supported database, accessed through Drizzle ORM. Any standard PostgreSQL-compatible database works — you bring your own and set DATABASE_URL.

  • A running PostgreSQL instance (version 14 or later recommended)
  • DATABASE_URL set in your .env file

Copy .env.example to .env and set the connection string:

Terminal window
DATABASE_URL="postgresql://cauldron:cauldron@localhost:5432/cauldron"

The format is standard postgresql://user:password@host:port/database. The app reads this variable at startup — if it is missing, the process will exit with an error.


The database client is selected automatically based on your DATABASE_URL. You do not configure this manually.

src/lib/db/index.ts

If the URL contains neon.tech or neon.db, the app uses the Neon serverless driver (@neondatabase/serverless + drizzle-orm/neon-http). For every other URL it falls back to the standard pg pool (pg + drizzle-orm/node-postgres).

URL containsDriver usedBest for
neon.tech or neon.dbNeon serverless (HTTP)Vercel edge/serverless functions
Anything elsepg PoolTraditional servers, Docker, Supabase, RDS, Railway

This matters for deployment. Neon’s HTTP driver works in edge runtimes that prohibit persistent TCP connections. The pg pool requires a long-lived process and a real TCP socket, which is exactly what you have with Docker or a VPS.

Vercel deployments: Use Neon. Its connection string contains neon.tech, so the correct driver is picked up automatically.

Docker or self-hosted deployments: Use any standard connection string. The pg pool is used and will maintain a connection pool for you.


Any provider that speaks standard PostgreSQL works. Tested and known-good options:

ProviderNotes
NeonRecommended for Vercel. Free tier available. Serverless driver selected automatically.
SupabaseStandard Postgres connection string. Use the “direct connection” URL, not the pooler, for migrations.
AWS RDSStandard connection string. Ensure security groups allow outbound from your app host.
RailwayProvides a DATABASE_URL environment variable out of the box — paste it directly.
DockerThe included docker-compose.yml runs Postgres 16 locally. Default URL is postgresql://cauldron:cauldron@localhost:5432/cauldron.
Any standard PostgresIf it accepts a postgresql:// connection string, it works.

The schema is defined in src/lib/db/schema.ts. There are four logical groups of tables.

These are required by NextAuth.js via the Drizzle adapter.

TablePurpose
usersRegistered accounts. Stores email, name, avatar, role (admin or member), daily generation limit, and video access flag.
accountsOAuth provider link records. One user can have multiple connected accounts (e.g., Google).
sessionsActive session tokens with expiry timestamps.
verification_tokensShort-lived tokens used for email verification flows.
TablePurpose
brandsNamed brand workspaces with a display color. Used to organize assets by project or client.
assetsEvery generated image or video. Stores the prompt, model, provider, storage keys, dimensions, file size, cost estimate, and for videos the duration and audio flag.
asset_brandsJunction table linking assets to brands (many-to-many).
asset_tagsFreeform text tags applied to assets. Indexed for fast tag-based search.
TablePurpose
generationsAudit log of every generation attempt. Tracks status (pending, processing, completed, failed), the provider job ID for async polling, cost estimate, XP earned, and duration. A row is created when generation starts; the asset_id is populated on success.
TablePurpose
user_xpRunning XP total and current level for each user. One row per user.
xp_transactionsLedger of every XP event: generation, badge_reward, or admin_grant. Linked to the generation that triggered it where applicable.
badgesBadge definitions — id, name, description, Lucide icon name, category, XP reward, and display order. Populated by the seed script.
user_badgesRecords which users have earned which badges, with timestamps.

See XP, Levels, and Feats for details on how the gamification system works in practice.


Drizzle Kit manages schema migrations. The config lives in drizzle.config.ts and reads DATABASE_URL from .env.local.

During active development, db:push introspects your schema and applies changes directly without generating migration files. It is fast and convenient for iterating on schema changes locally.

Terminal window
bun run db:push

Use db:push for local development only. It does not create migration files and is not safe to run against a production database.

For production deployments, generate and apply versioned migration files. Migration files are stored in drizzle/ and should be committed to version control.

Terminal window
bun run db:migrate

This applies any pending migrations in drizzle/ in order. It is idempotent — already-applied migrations are skipped.

Drizzle Studio opens a browser-based UI for inspecting and editing your database.

Terminal window
bun run db:studio

Studio connects to the database specified by DATABASE_URL in .env.local and opens at https://local.drizzle.studio.


After your first migration, run the badge seed script to populate the badges table. The app will not award feats until these rows exist.

Terminal window
bunx tsx src/lib/db/seed-badges.ts

The script is safe to run multiple times — it uses INSERT ... ON CONFLICT DO UPDATE, so it updates existing records and adds any new ones. It also removes any legacy badge IDs that are no longer in the current definition list.

The following badges are seeded:

CategoryBadgeCondition
MilestoneFirst BrewFirst generation
MilestoneCentaur100 generations
MilestoneHydra1,000 generations
StreakKindling7-day streak
StreakInferno30-day streak
ModelRanger5+ different image models used
QualitySigil50+ assets tagged with brands
VideoIllusionistFirst video generation
VideoConjurer50 video generations
SpecialEarly AdopterJoined in the first month
SpecialAdminTeam administrator
SpecialFounderInstance creator and owner

  1. Set DATABASE_URL in your .env file.
  2. Run bun run db:migrate (production) or bun run db:push (development).
  3. Run bunx tsx src/lib/db/seed-badges.ts to populate badge definitions.
  4. Start the app — bun run dev or bun run start.