This guide starts the full local stack:
- Next.js app (UI + API) on
http://localhost:3001 - PostgreSQL database (set
DATABASE_URL; Neon or local Postgres) - Full signer-dmz (Apache + JWT + go-livepeer) via the repo root
docker-compose.yml(same image path as production)
- Node.js + npm
- Docker + Docker Compose
- A PostgreSQL instance and
DATABASE_URLconnection string
npm installCreate your local env file:
cp .env.example .envIf you also use .env.local, remember Next.js precedence: .env.local overrides .env.
Keep NEXTAUTH_SECRET consistent across files (or only set it in one place) to avoid
session cookie decrypt errors.
Minimum required for local startup:
NEXTAUTH_SECRET(set to any long random string)DATABASE_URL(PostgreSQL connection string; migrations run onnpm run dev/npm run build)SIGNER_INTERNAL_URL(default works with compose)SIGNER_NETWORKandETH_RPC_URL(defaults work)
Generate a strong secret with:
openssl rand -base64 32Optional (only if needed):
- Google/GitHub OAuth vars (for OAuth login)
- Turnkey Wallet Kit public IDs (
NEXT_PUBLIC_ORGANIZATION_ID,NEXT_PUBLIC_AUTH_PROXY_CONFIG_ID) for embedded wallet login
npm run oidc:seed in step 3 is only required the first time (or if keys were removed);
see also ## OIDC seed and client registration below.
Migrations run on predev (npm run db:prepare). On a new database, create the
OIDC signing key once (before step 4, which needs GET /api/v1/oidc/jwks):
npm run oidc:seedThen start the app:
npm run devOpen:
- Dashboard:
http://localhost:3001 - Login:
http://localhost:3001/login - Health:
http://localhost:3001/api/v1/health
The local stack uses the full signer-dmz image from docker/signer-dmz/Dockerfile (same as
production: Apache, JWT verification, and go-livepeer in one process). The container
must be able to download the OIDC public key at start time, so the app in step 3
must be running before the first docker compose up. Default DMZ URL:
http://127.0.0.1:8080 — match SIGNER_INTERNAL_URL and SIGNER_CLI_URL in .env.
If you do not use port 3001 for the app, set OIDC_ISSUER, OIDC_AUDIENCE, and
JWKS_URI in .env to match your NEXTAUTH_URL and a JWKS URL reachable from the
container (for example http://host.docker.internal:<port>/api/v1/oidc/jwks).
docker compose up -d --buildCheck signer logs (optional):
docker compose logs -f signer-dmzIn another terminal:
DATABASE_URL='postgresql://...' npm run bootstrapThis creates an admin user and prints a pmth_... bearer token. The token is valid for 1 year.
Optionally specify an email for the admin user:
DATABASE_URL='postgresql://...' npm run bootstrap admin@example.comIf an admin already exists, the script issues a new token for the existing admin instead of creating a new user.
Using the token:
- Web login: Paste the token into the login page at
http://localhost:3001/login - API requests: Use the
Authorizationheader:
curl -H "Authorization: Bearer pmth_..." http://localhost:3001/api/v1/signersOnce logged in, you can issue additional remote-signer tokens from the admin dashboard.
After migrations, initialize OIDC signing keys:
npm run oidc:seedThen register application clients through the dashboard/API and rotate secrets per app from the credentials endpoint (/api/v1/apps/{clientId}/credentials). See docs/builder-api.md for OIDC, Builder, and Usage API integration.
# Start signer
docker compose up -d --build
# Stop signer
docker compose stop signer-dmz
# Stop and remove signer container
docker compose down
# Run linter
npm run lintPymthouse can be deployed to Vercel (for the Next.js app) with the Docker signer running on Railway, Render, or Fly.io.
Quick Deploy (15 minutes): See docs/DEPLOYMENT.md for a quick checklist.
Detailed Guide: See docs/vercel-deployment.md for full step-by-step instructions including:
- Deploying the go-livepeer signer to Railway/Render/Fly.io
- Deploying the Next.js app to Vercel
- Configuring environment variables
- Setting up PostgreSQL (Neon/Vercel Postgres)
- OAuth callback URLs
- Custom domains
Files included:
vercel.json- Vercel configurationdocker/signer-dmz/— go-livepeer signer Dockerfiles, Apache JWT DMZ,docker-compose.yml, andscripts/jwks_to_pem.py(see docker/signer-dmz/README.md)railway.json- Railway configurationrender.yaml- Render Blueprint
Signer is not running/ DMZ 401 on/api/signer/*: Apacheissmust matchgetIssuer()(yourNEXTAUTH_URL+/api/v1/oidc). The compose stack passesNEXTAUTH_URLinto the container so the entrypoint can setOIDC_ISSUER/JWKS_URI; keep it identical to the URL you use for the Next app (localhostvs127.0.0.1vs a LAN hostname must match). After changingdocker-compose.ymlorentrypoint.sh, rebuild:docker compose up -d --build.- App can’t open DB: verify
DATABASE_URLand thatnpm run db:preparesucceeds. - OAuth buttons fail: set provider credentials in
.envor use token login fromnpm run bootstrap. - Repeating
JWT_SESSION_ERROR/JWEDecryptionFailed:- Ensure one stable
NEXTAUTH_SECRETvalue (watch.env.localoverriding.env). - Clear
localhostcookies (or open a private window), then sign in again.
- Ensure one stable