First-Admin Setup
Bootstrapping the first administrator account on a freshly deployed registry.
A freshly deployed registry has Postgres, but no rows in it: no migrations applied, no users.
Two things happen in sequence to get from "just deployed" to "ready to receive a push:all."
Migrations run automatically
npm run build:registry runs scripts/migrate-on-deploy.mjs ahead of next build, applying any
pending journaled migrations in a single process that's awaited to completion. There's no
separate db:migrate step to remember in a Vercel deploy.
instrumentation.ts runs the same migration path (src/app/lib/db/auto-migrate.ts) at Node
process startup for local and Docker runs, but it returns early when VERCEL is set: a
serverless instance is frozen the moment its first response goes out, which left the migration
advisory lock held and deadlocked a real deploy. On Vercel, the build owns migrations.
/setup creates the first admin
With no users in the database, visiting the registry redirects to /setup:
- Enter an email and password for the first admin account.
- On submit, you're redirected to
/login?setup=1. - Sign in with those credentials.
/setup itself checks getUserCount(): if a user already exists, it redirects straight to
/login instead (so re-visiting /setup after the first admin exists is a no-op redirect, not an
error). Outside registry mode (no DATABASE_URL), /setup just explains that setup only applies
to a registry.
After the first admin signs in, further users are added via invite, see Auth → Roles & invites.
Non-interactive alternative: npm run db:seed / db:bootstrap
For scripted deploys where visiting /setup in a browser isn't practical, set
HANDOFF_ADMIN_EMAIL and HANDOFF_ADMIN_PASSWORD and run one of two scripts against the
registry's database:
npm run db:bootstrap: creates the first admin from those two env vars. No-ops (logs "Users already exist") if any user already exists.npm run db:seed: does the same admin bootstrap, plus seeds components/patterns/token snapshots from a localpublic/apiJSON directory if present. Mainly useful for local Docker/dev registries seeded from static fixtures, not a normal production deploy path.
Both are idempotent with respect to the admin user, safe to include in a deploy script that might run more than once.
Troubleshooting: /setup crashes with "relation 'user' does not exist"
This means migrations haven't run yet. Check the deploy's function logs for
[handoff] auto-migrate: lines; a migration failed: line names the actual SQL error. Manual
recovery, once the underlying issue is fixed:
curl -X POST https://your-registry.example.com/api/admin/migrate \
-H "Authorization: Bearer $HANDOFF_SYNC_SECRET"If migrations are corrupt mid-state, resetting the schema and redeploying (or re-hitting
/api/admin/migrate) is the escape hatch:
DROP SCHEMA public CASCADE;
CREATE SCHEMA public;Destructive
DROP SCHEMA public CASCADE deletes every row in the registry's database. Only reach for this
on a registry that hasn't received real pushed content yet, or where you've confirmed the data
is recoverable from the workspace side (re-push:all after re-migrating).