Captures the non-obvious gotchas that make bringing up a local instance painful: the collaboration server is a THIRD process (pnpm dev starts only API + client) that must be built before running (tsx/ts-node fail on NestJS DI); APP_SECRET must be identical between the API and collab servers or every realtime connection is rejected with "Invalid collab token"; Vite binds localhost so LAN access needs --host; a stale @docmost/editor-ext white- screens the client; pgvector is mandatory; migrations don't auto-run in dev. Also documents that demo/test passwords should be a simple one-word alphanumeric (no special chars, which get mangled through shells/JSON/URLs). Referenced from AGENTS.md (Commands + Two-server-processes sections). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5.8 KiB
Running a local dev stand
How to bring up a working local instance (API + client + realtime collaboration) and the non-obvious gotchas that will otherwise eat an hour. Written from real setup pain — read the Gotchas section before you start.
Prerequisites
- Node 20+ / pnpm 10+.
- Postgres with pgvector. Use the
pgvector/pgvectorimage (e.g.pgvector/pgvector:pg18). The stockpostgresimage will FAIL theCREATE EXTENSION vectormigration — the RAG feature stores embeddings inpage_embeddings. - Redis — backs caching, BullMQ queues, the Socket.IO adapter, and collab sync.
1. Environment (.env)
The client (apps/client/vite.config.ts) and both server processes read env via
envPath → the workspace root .env. Keep a single source of truth. Minimum:
APP_URL=http://localhost:3000
PORT=3000
APP_SECRET=<one long secret — SAME value everywhere, see gotcha #3>
DATABASE_URL="postgresql://<user>:<pass>@localhost:5432/<db>?schema=public"
REDIS_URL=redis://127.0.0.1:6379
COLLAB_URL=http://localhost:3001 # where the CLIENT connects for realtime
COLLAB_PORT=3001 # where the COLLAB server listens
STORAGE_DRIVER=local
DISABLE_TELEMETRY=true
If you also keep an
apps/server/.env, itsAPP_SECRETmust match the root one (see gotcha #3).
2. Migrations
Migrations do not auto-run in local dev. After a fresh checkout or switching branches, apply them yourself or endpoints touching a new column/table will 500:
pnpm --filter server migration:latest
3. Bring it up — THREE processes, not two
pnpm dev starts only the API server (Nest, :3000) and the client
(Vite). Realtime collaboration is a separate process and pnpm dev does NOT
start it. You need all three:
# 1) API + client (from the repo root)
pnpm dev
# → API http://localhost:3000
# → client http://localhost:5173 (Vite; localhost-only by default)
# 2) Collaboration server — SEPARATE process. Build first (see gotcha #2), then:
pnpm --filter server build # produces dist/collaboration/server/collab-main.js
pnpm collab:dev # node dist/.../collab-main → listens on :3001 (0.0.0.0)
Without step 2 the editor shows "Real-time editor connection lost. Retrying…", stays in read-only static mode, and anything that only mounts in the live editor won't appear.
Seeding a login
Register through the UI, or reset an existing user's password directly in the DB
(the server hashes with bcrypt):
// node -e '...' with pg + bcrypt from the repo's node_modules
const bcrypt = require("bcrypt");
const { Client } = require("pg");
(async () => {
const hash = await bcrypt.hash("demopass", 10);
const c = new Client({ /* DATABASE_URL parts */ });
await c.connect();
await c.query("update users set password=$1 where email=$2", [hash, "admin@example.com"]);
await c.end();
})();
Use a simple one-word password with no special characters (e.g.
demopass, notStr0ng!Pass@2026). Demo/test credentials get passed through shells, JSON payloads, and URLs by scripts and automation, where!@$&etc. get mangled or need escaping — a plain alphanumeric word avoids a whole class of "wrong password" confusion.
Gotchas (the грабли)
-
Collaboration is a third process.
pnpm devruns API + client only. Startpnpm collab:dev(on:3001) separately or the live editor never connects. The client connects toCOLLAB_URLdirectly (defaulthttp://localhost:3001), NOT through the Vite/collabproxy — the API server on:3000does not serve the collab websocket. -
The collab server must be built — you can't run it from source.
collab:devrunsnode dist/collaboration/server/collab-main.js, so runpnpm --filter server buildfirst. Running the entry viatsx/ts-nodefails with a NestJS DI error ("dependency … appears to be undefined at runtime") because direct TS execution doesn't emit the decorator metadata the built output has. -
APP_SECRETmust be identical for the API server and the collab server. The API issues a collab-token (JWT signed withAPP_SECRET); the collab server validates it withAPP_SECRET. If they load different values (e.g. a root.envand anapps/server/.envwith different secrets), every realtime connection is rejected with[onAuthenticate] Invalid collab tokenand the editor shows "connection lost". Keep one secret everywhere. -
Vite binds localhost only. To reach the stand from another machine on the LAN, start the client with
--host(pnpm --filter client exec vite --host) and use the box's LAN IP. The/api,/socket.io, and/collabVite proxies forward toAPP_URL, so the API just works over the LAN; realtime needsCOLLAB_URLreachable from the browser (point it at the LAN IP:3001, and run collab on0.0.0.0— it does by default). -
A stale
@docmost/editor-extwhite-screens the client. The client imports from@docmost/editor-ext(a workspace package). If that package's source is behind (missing a newer export, e.g.Spoiler), the client dies at load with "The requested module … does not provide an export named 'Spoiler'" → blank page. Make sure the workspacepackages/editor-extis current for the branch you're running (a stale sibling checkout resolved through a sharednode_modulessymlink is the usual cause). -
pgvector, not stock postgres (see Prerequisites) — the
vectorextension migration fails otherwise. -
Migrations don't auto-run in dev — run
migration:latestafter every pull or branch switch.
See also the Commands and Architecture → Two server processes sections in
AGENTS.md.