Skip to main content
Version: Sprint 4

Physical View

Deployment and infrastructure as defined in application dev at 01c423c (11 October 2026).

Self-Hosted Runtime​

Dockerfile builds with Node 24, installs using npm ci --legacy-peer-deps, and copies the Nitro .output directory into the runtime image. The container starts node .output/server/index.mjs and exposes port 3000.

Dockerfile.yjs installs the independent yjs/package.json dependencies and starts yjs-server.mjs on port 1234. The local deployment workflow runs this container with host networking; the app container maps 3000:3000 and adds host.docker.internal pointing to the host gateway.

Collaboration Network Path​

  • Development defaults to ws://localhost:1234; npm run dev starts Vite and the standalone Yjs server concurrently.
  • Production clients default to ws(s)://<app-origin>/yjs-ws, with the file ID appended by WebsocketProvider as the room name.
  • VITE_YJS_WS_URL overrides the browser endpoint at build time.
  • The Node listener plugin forwards raw upgrade headers and bytes to YJS_WS_UPSTREAM, defaulting to host.docker.internal:1234.
  • The dedicated server uses the URL's room name. Preserve the same room naming across proxy configurations; adding/removing URL prefixes can change the PostgreSQL persistence key.
  • The application checks owner/contributor access before connecting its normal editor client. The standalone WebSocket handler itself does not validate Better Auth sessions; do not describe the client pre-check as server-side room authorization.

The server records non-replay updates in yjs_updates, replays a room's rows ordered by ID when opened, and compacts the loaded history when it exceeds YJS_COMPACT_AFTER (default 1000). Persistence failures are logged. Room persistence is separate from saving a versioned JSON document.

Build and Deployment Workflows​

All three workflows target main; the coverage workflow also runs for pull requests targeting main. Working on dev alone does not trigger these deployment workflows.

The Vercel workflow sets NITRO_PRESET=vercel. It does not deploy the dedicated Yjs container. Vercel is therefore an alternative application target, not evidence of a functioning same-origin Node WebSocket listener there; configure a separately reachable collaboration service when that listener is unavailable.

Configuration Contract​

Names below come from source, not secret values. Keep credentials out of docs, source control, and browser-exposed VITE_ variables.

VariableConsumer and purpose
DATABASE_URLApp database pool, Better Auth pool, and Yjs persistence pool
BETTER_AUTH_URL, BETTER_AUTH_SECRETAuth origin and session/signing configuration
GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRETGitHub OAuth in lib/auth.ts
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRETGoogle OAuth in lib/auth.ts
FILE_SERVER_URL, INTERNAL_SECRETDocument-byte storage URL and X-Internal-Key
NITRO_PRESETBuild target, e.g. node_server or vercel
VITE_YJS_WS_URLOptional browser collaboration URL, embedded at build time
YJS_WS_UPSTREAMNode app's TCP upstream host/port
YJS_WS_PORTDedicated server port, default 1234
YJS_COMPACT_AFTERLoaded room-history compaction threshold, default 1000
DAILY_TIMEZONEDaily date boundary, default Africa/Johannesburg
GROQ_API_KEYOptional server key for AI assistance; the API can also receive x-groq-key

Configuration Gaps in dev​

These are repository observations, not fixes made by this documentation update:

  • The Docker deployment workflow exports HUB_CLIENT_ID and HUB_CLIENT_SECRET, but auth code reads GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET. Align the deployment environment with the names actually consumed by the application.
  • The workflow does not explicitly pass the optional Groq, timezone, upstream, or browser URL settings. Defaults/user-provided keys may apply; provisioning custom values needs a deployment change.
  • Comments reference sql/collab.sql and sql/problems.sql, but the checked tracked tree contains no SQL migration files. Existing database provisioning must provide the tables/constraints in Database View; do not assume startup creates them.
  • The Yjs subproject lockfile is behind its manifest. Its Dockerfile currently uses npm install --omit=dev, rather than a lockfile-only reproducible install.

Persistence and Scaling Boundaries​

PostgreSQL holds identity, file metadata, invites, problem/daily state, and Yjs update history. External storage holds saved document bytes. Browser preferences and temporary editing state are not database backups. The AI endpoint's credit log is in-process, so it is not a distributed limit across replicas.

PostgreSQL update history enables room restoration, but it does not coordinate live broadcasts among multiple independent Yjs servers. Scaling the application should retain consistent room routing or introduce an explicitly coordinated collaboration backend. No Redis, PgBouncer, replica, or cluster is configured in this repository.