Self-hosting and setup
Everything needed to run NextForge: the services, their settings, and how to run, migrate and deploy them.
The pieces
| App | What | Port |
|---|---|---|
apps/api | NestJS API, the system of record. REST plus Socket.IO for live boards, chat and doc co-editing. | 3010 |
apps/web | Next.js app, this site and this guide. | 3000 |
apps/admin | Superadmin panel (Vite): organizations, platform settings, mobile app uploads. | 5174 |
apps/ai-backend | Zenit, the AI service (FastAPI on Groq's free tier). Only the API talks to it. | 8000 |
apps/scanner | Go worker that runs Trivy (dependencies) and OWASP ZAP (live app) scans. | 8090 |
apps/mobile | Flutter Android app, released as an APK. | n/a |
Also needed: PostgreSQL and Redis. Optional: an S3 bucket (uploads), OneSignal (email and push), a GitHub App, Google OAuth (calendar), a Slack webhook.
Run it locally
Needs Node 20+, pnpm (via corepack), Python 3.12 for Zenit, and Go only if you run the scanner.
corepack enable
pnpm install
pip install -r apps/ai-backend/requirements.txt
# database tables
pnpm --filter nextforge-api run migration:run
# the first admin: prints a one-time password
pnpm --filter nextforge-api run seed:admin -- you@company.com "Your Name" "Your org"
# api, web, admin, Zenit and the scanner together
pnpm run devThe seeded admin is platform staff and owns a first workspace. Staff must turn on two-step sign-in (an authenticator app) under Account before the admin panel opens.
Before committing, run pnpm run check: typecheck, tests, lint, and a rule that no API file goes over 400 lines.
Settings
Each app reads a .env file next to it. These files are gitignored; never commit them. Settings marked * are required: the API refuses to start without them rather than run with a weak default. Make random keys with openssl rand -hex 32.
API: core
| Setting | What it is |
|---|---|
DATABASE_URL * | Postgres connection string (or DATABASE_HOST, _PORT, _NAME, _USER, _PASSWORD, _SSL) |
REDIS_HOST | Required in production (sessions and rate limits are shared through it). Plus REDIS_PORT, REDIS_PASSWORD, REDIS_TLS |
JWT_SECRET * | Signs access tokens. At least 32 characters. |
DATA_ENCRYPTION_KEY * | Encrypts personal data at rest. 64 hex characters. |
BLIND_INDEX_KEY * | Lets encrypted emails be looked up. 64 hex characters, different from the one above. |
WEB_URL | Where the web app lives; used in emails and redirects |
STUDIO_ORIGINS | Comma-separated origins allowed by CORS (the web and admin URLs) |
PUBLIC_SIGNUP | true lets anyone create a workspace |
PORT | Defaults to 3010 |
API: services
| Setting | What it is |
|---|---|
ZENIT_URL | Zenit's address, default http://127.0.0.1:8000 |
ZENIT_API_KEY | Shared secret with Zenit; same value on both sides |
SCANNER_SERVICE_URL | The scanner's address |
AWS_REGION, AWS_BUCKET_NAME, AWS_ACCESS_KEY, AWS_SECRET | S3 for uploads. Everything goes under STORAGE_PREFIX (default nextforge/). Without these, uploads answer 503. |
ONESIGNAL_APP_ID, ONESIGNAL_API_KEY | Email and phone push |
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_REDIRECT_URI | Google Calendar connection |
SLACK_WEBHOOK_URL | Failed-build alerts to Slack |
GITHUB_APP_*, GITHUB_WEBHOOK_SECRET | See GitHub App below |
Other apps
| Setting | What it is |
|---|---|
NEXT_PUBLIC_API_URL * | Web: the API's public URL (also used for the live socket) |
VITE_API_URL * | Admin: the API's public URL |
GROQ_API_KEY * | Zenit: a free key from console.groq.com |
ZENIT_API_KEY * | Zenit: the same shared secret as on the API |
Zenit stays on Groq's free tier on purpose. Long jobs, like turning a PRD into a backlog, are split into many small calls that fit its per-minute limit, and pause (resumable) if the daily limit runs out.
GitHub App
Create it once; every workspace connects to it from Settings → Integrations.
- GitHub → Settings → Developer settings → GitHub Apps → New GitHub App (under your organization if the repos are there).
- Callback URL
https://API/integrations/github/callback, webhook URLhttps://API/integrations/github/webhook, a long random webhook secret. Turn on Request user authorization (OAuth) during installation: NextForge uses it to check who is installing. - Repository permissions: Issues read and write; Contents, Pull requests, Actions read; Metadata read. Events: Issues, Issue comment, Push, Pull request, Create, Workflow run.
- Generate a client secret and a private key (.pem).
GITHUB_APP_ID=123456
GITHUB_APP_SLUG=nextforge
GITHUB_APP_CLIENT_ID=Iv23li...
GITHUB_APP_CLIENT_SECRET=...
GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----\n"
GITHUB_WEBHOOK_SECRET=...Restart the API and Settings → Integrations shows Connect GitHub to owners.
Deploy
Pushing to main or develop runs the publish workflow: it builds the API, web and scanner images, pushes them to the GitHub Container Registry, and deploys over SSH. A separate workflow scans the images with Trivy (vulnerabilities, SBOM, secrets).
To try the image on one machine first: put the API settings in docker/app.env and run docker compose up -d --build (it brings Redis; point the database settings at your Postgres).
Database migrations run by themselves: in production the API applies any pending ones when it starts, before it takes traffic, each in a transaction. If one fails, the API doesn't start and the error is in its log. Locally, run migration:run yourself.
Good to know
- Every request records which app sent it (web, admin, or the phone app with its version) in the audit log, the sessions list and the server's request log.
- Live doc co-editing keeps each open page in the API's memory. That's fine on one API instance; running several needs a shared store and the Socket.IO Redis adapter first.