Files
BizGaze_Remote/docker-compose.yml
T
Sravan 63f2c588da feat(scale): swappable pub/sub layer + fix chat.js missed awaits (Phase 6)
Two things:

1. FIX a live regression the async conversion missed: chat.js calls repos via the
   lazy repos() helper (not the R. prefix), so my sweep skipped it — effectiveStatus
   / broadcastPresence read `repos().users.byId(userId)` synchronously, but that's a
   Promise now, so presence broadcasts always reported status 'active' and dropped
   last_seen. Now awaited (effectiveStatus/broadcastPresence async); touchSeen is a
   fire-and-forget UPDATE with .catch. Audited all non-R. repo calls — only chat.js
   was affected (media.js backfill was already awaited).

2. Swappable pub/sub for multi-instance real-time fan-out (the actual blocker to
   running >1 instance — not the DB). server/pubsub.js picks a backend by
   PUBSUB_BACKEND (default 'memory'). Local socket delivery is UNCHANGED; publish is
   additive — memory = no-op (zero hot-path cost, identical single-instance
   behaviour), redis = fan-out to other instances with a self-echo guard. chat.js
   pushToUser/broadcastPresence now also publish; each instance subscribes to deliver
   remote events to its local sockets. Interface is tiny so Redis is one swappable
   file (Postgres LISTEN/NOTIFY or NATS could drop in the same way — never hardwired,
   as requested). Dormant redis service added to compose behind the 'scale' profile;
   redis dep added; PUBSUB_BACKEND/REDIS_URL documented.

Validated: smoke 22/22 (memory), e2e chat delivery green. NOTE: full multi-instance
also needs distributed presence (isOnline is per-process) + meeting-signaling
sharing — chat/presence fan out via this layer; those are follow-ups.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 23:20:02 +05:30

114 lines
5.3 KiB
YAML

# BizGaze Support — deployed behind the existing Nginx Proxy Manager.
# No host ports are published: NPM reaches this container by name on the
# shared `nginx_proxy_manager_default` network. TLS is terminated by NPM,
# which proxies https://remote.bizgaze.com -> bizgaze-support:8090.
services:
app:
build: .
image: bizgaze-support:latest
container_name: bizgaze-support
restart: unless-stopped
environment:
- PORT=8090
- DB_PATH=/data/data.db
# Desktop installers + auto-update feed live on the persistent volume so uploaded
# builds survive image rebuilds (a plain image path would be wiped on every deploy).
- DOWNLOADS_DIR=/data/downloads
# Chat uploads / recordings / transcripts on the persistent volume too, so they survive image
# rebuilds (otherwise old shared images 404 as "broken image" after every deploy).
- UPLOADS_DIR=/data/uploads
# Max chat-attachment size in MB (default 1024 = 1 GB). The app streams uploads to /data/uploads, so
# large files don't buffer in memory. IMPORTANT: also set Nginx Proxy Manager's client_max_body_size
# for remote.bizgaze.com to at least this (Advanced tab: `client_max_body_size 1024m;`) or the proxy
# rejects big uploads before they reach the app.
- MAX_UPLOAD_MB=1024
- REC_DIR=/data/recordings
- TRANS_DIR=/data/transcripts
# Secrets (TURN credentials, SSO_SECRET, BIZGAZE_WEBHOOK_URL, etc.) live in
# a .env file next to this compose file. It is gitignored — never committed.
# See .env.example for the expected keys.
env_file:
- path: .env
required: false
volumes:
- bizgaze_support_data:/data # persists data.db across rebuilds
networks:
- npm
# Wait for Postgres to be healthy before starting. Only matters when DB_BACKEND=pg (else the app uses
# the local SQLite file and ignores pg), but it's harmless on SQLite — pg comes up in a second or two.
depends_on:
bizgazepg:
condition: service_healthy
# PostgreSQL — the app's data store when DB_BACKEND=pg (default is the local SQLite file). Distinct
# service/container name so it never collides with the other postgres containers on the shared NPM
# network; the app reaches it as `bizgaze-postgres`. Data on its own named volume.
bizgazepg:
image: postgres:16
container_name: bizgaze-postgres
restart: unless-stopped
environment:
- POSTGRES_USER=bizgaze
- POSTGRES_DB=bizgaze
# Password comes from the same .env as the app (compose interpolates ${...} from the .env in this dir).
# NOTE: Postgres only applies POSTGRES_PASSWORD on FIRST init of an empty data volume.
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-bizgaze_local}
volumes:
- bizgaze_pg_data:/var/lib/postgresql/data
networks:
- npm
healthcheck:
test: ["CMD-SHELL", "pg_isready -U bizgaze -d bizgaze"]
interval: 5s
timeout: 3s
retries: 12
# Redis — cross-instance real-time fan-out (chat/presence), used only when PUBSUB_BACKEND=redis. Dormant
# by default (behind the 'scale' profile, like livekit), so a normal deploy never starts it and the app
# stays single-instance on the in-memory pubsub. To run multiple app instances: start this
# (`docker compose --profile scale up -d`), set PUBSUB_BACKEND=redis + REDIS_URL in .env, and put the app
# behind a load balancer with sticky sessions for the /ws WebSocket. (Meeting SIGNALING state is still
# per-process — cross-instance meetings need sticky routing or further work; chat/presence fan out here.)
bizgazeredis:
image: redis:7-alpine
container_name: bizgaze-redis
restart: unless-stopped
profiles: ["scale"]
networks:
- npm
# LiveKit SFU — meeting media server. Optional: only started/used when the app's .env has
# LIVEKIT_URL/API_KEY/API_SECRET set (otherwise meetings use the built-in P2P mesh). NPM proxies
# wss://livekit.bizgaze.com -> livekit:7880 (signaling); media flows over the published UDP/TCP
# ports below, NOT through NPM. Single-node (no Redis) — consistent with the app's single-instance rule.
livekit:
# v1.8+ implements the /rtc/v1 signaling path (protocol 17) that the bundled
# livekit-client@2.20 uses. On the older v1.7 the client fell back to the legacy path and
# track publishing broke (mic/cam wouldn't turn on). Keep this within one minor of the client.
image: livekit/livekit-server:v1.9
container_name: bizgaze-livekit
restart: unless-stopped
# Dormant by default: a normal `docker compose up -d` / deploy.sh does NOT start it. Enable SFU
# explicitly with `docker compose --profile sfu up -d` after setting the LIVEKIT_* vars (see DEPLOY.md).
profiles: ["sfu"]
command: --config /etc/livekit.yaml
environment:
# key: secret, sourced from the same .env as the app so both sign/verify with the same secret.
- "LIVEKIT_KEYS=${LIVEKIT_API_KEY}: ${LIVEKIT_API_SECRET}"
volumes:
- ./livekit.yaml:/etc/livekit.yaml:ro
ports:
- "7881:7881" # WebRTC over TCP (fallback)
- "50000:50000/udp" # single WebRTC media UDP port (must match livekit.yaml rtc.udp_port)
networks:
- npm
networks:
npm:
external: true
name: nginx_proxy_manager_default
volumes:
bizgaze_support_data:
bizgaze_pg_data: