Registry

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:

  1. Enter an email and password for the first admin account.
  2. On submit, you're redirected to /login?setup=1.
  3. 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 local public/api JSON 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:

Terminal
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).

On this page