ShipAgent

Database

Connect a PostgreSQL database, apply every migration and create the site owner's account.

The site stores users, sessions, orders and credits in a PostgreSQL database you connect once; it works when this check passes:

pnpm tsx scripts/check-database.ts

Its last line must be Database: all checks passed. Every other integration needs this one first.

You

Create one database and paste its connection string when the agent asks. Then name the email the site owner will sign in with.

Value Where you get it
DATABASE_URL (Neon, recommended) console.neon.tech → New project (any name, the region closest to your users) → Create → on the project dashboard click Connect → keep Connection pooling on → copy the connection string. Its host contains -pooler.
DATABASE_URL (Supabase) supabase.com/dashboard → New project (set a database password and keep it) → open the project → Connect (top bar) → Connection string → Transaction pooler (port 6543) → copy, then replace [YOUR-PASSWORD] with your password.
DATABASE_URL (any other Postgres) your provider's connection string: postgres://user:password@host:port/database
Owner email the email you will sign in with as the site owner

The dashboard labels are as of late 2026 (check the dashboard, menus move). If one has moved, the connection string is always behind the project's Connect button.

On Neon, open the project and click Connect:

Keep Connection pooling on and copy the string:

Give the app the pooled string. ShipAgent recognizes Neon and Supabase hosts and sets the connection options a pooler needs. On Cloudflare the deployed site does not use DATABASE_URL directly: it connects through Hyperdrive, which the deploy step creates from the same string.

Agent

  • Writes DATABASE_URL into .env.local and runs the check to confirm it connects.
  • Applies every migration in lib/db/migrations/ with pnpm db:migrate. If the database already holds data, it asks you first.
  • Never uses db:push: a database made that way has no migration record and breaks the next migration.
  • Runs the check again: every table and column exists, the latest migration is applied, and a test write is read back and rolled back.
  • Creates the owner account with pnpm create-superadmin <your email>. That user gets the superadmin role and a verified email.
  • Runs the check until it passes, then moves on to auth.

There is no password. The owner signs in with an email code (after Email) or with Google or GitHub using the same email (after Auth); Google and GitHub accounts are linked to an existing user with the same email.

What a pass looks like

✓ connected to <host> — PostgreSQL <version>
  app connection profile: <profile> (ssl <setting>, prepared statements <on|off>)
✓ all <n> tables of lib/db/schema.ts exist with every column
✓ migrations applied up to <latest migration> (<n> in lib/db/migrations)
✓ write + read inside a transaction works (rolled back, nothing kept)
✓ superadmin: you@yourdomain.com

Database: all checks passed

Before the agent migrates, the table lines are ✗; that is expected. The last ✗ to disappear is no superadmin in the user table, which the owner account step fixes.

When it fails

The check says What it means Fix
DATABASE_URL is not a postgres:// connection string a dashboard URL was pasted, or the quotes are broken copy the string again from the provider's Connect panel
cannot connect … password authentication failed wrong password, or Supabase's [YOUR-PASSWORD] is still in the string Supabase: Project Settings → Database → reset the password, paste the new string
cannot connect … timeout or ENOTFOUND wrong host, or a paused free-tier project copy the string again; restore a paused Supabase project from its dashboard first
cannot connect with profile self-hosted (ssl false) the host looked local, so encryption was turned off the agent adds ?sslmode=require to the URL (or ?sslmode=disable for a plain local Postgres)
table … does not exist or misses columns migrations not applied, or applied to another database the agent runs pnpm db:migrate against the same DATABASE_URL
drizzle.__drizzle_migrations does not exist while the tables exist the tables were made by hand or with db:push fresh database: drop it and migrate; database with data: the agent asks you before touching it
the migration fails on Supabase's transaction pooler (port 6543) the pooler does not suit migrations the agent runs the migration once with the Session pooler or Direct string and keeps the pooled string in .env.local
no superadmin in the user table the owner step was skipped or ran against another database tell the agent your email again

Going live

  • Create a separate database for production. Its DATABASE_URL goes into cloudflare/.env.production.local (Cloudflare) or the Vercel project's environment variables.
  • On Cloudflare, the new-site script creates the Hyperdrive config from that string (see the deploy-cloudflare skill).
  • The agent migrates production and creates the owner account there from your machine, pointing the same commands at the production string.
  • The check must pass against production too: pnpm tsx scripts/check-database.ts --env cloudflare/.env.production.local.