Skip to content

Feat/dockerize - #87

Open
Harxhit wants to merge 8 commits into
mainfrom
feat/dockerize
Open

Feat/dockerize#87
Harxhit wants to merge 8 commits into
mainfrom
feat/dockerize

Conversation

@Harxhit

@Harxhit Harxhit commented Aug 27, 2026

Copy link
Copy Markdown
Owner

Summary by cubic

Adds Docker Compose stacks for local development and production deployment, plus a Puppeteer load test that scales viewers to measure SFU capacity. Backend and frontend Dockerfiles are now multi-stage, and the broadcaster, viewer, and dashboard routes are public so the load test can reach them without auth.

Docker

  • Local and prod compose files run backend, frontend, MongoDB, a 6-node Redis cluster, and nginx ingress on network_mode: host (Linux only); TURN/relay services stay in infra/.
  • Dev targets hot reload; prod runs compiled TS and serves the static SPA through nginx, which proxies /backend and /socket.io.
  • The prod build copies rateLimit.lua into dist, and compose force-overrides Mongo/Redis endpoints so containerized infra is always used.
  • deploy/redis/init-cluster.sh initializes the cluster idempotently; deploy/env.docker.template and README.docker.md document required env vars.

Load test

  • load-test/sfu-capacity.js starts a real broadcaster, adds viewers in batches, and writes join and first-frame latency percentiles to CSV.
  • Frontend exposes __csRoomId, __csLiveAt, __csJoinedAt, __csFirstFrameAt, and __csSocket timing globals and connects the socket in Broadcaster.
  • Making those routes public removes auth from production; re-add ProtectedRoute if route security is still required.
  • Socket disconnect logs now include reason and details.

Written for commit 1a38dd1. Summary will update on new commits.

Review in cubic

Harxhit and others added 2 commits August 27, 2026 12:01
- add load-test script and results CSV for SFU capacity testing
- expose window globals for test timing (__csSocket, __csRoomId,
  __csLiveAt, __csJoinedAt, __csFirstFrameAt)
- wire socket connect/disconnect lifecycle in Broadcaster; use
  connectSocket in ViewerPage
- unprotect broadcaster/viewer/dashboard routes for load testing
- log socket disconnect reason and details at error level

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add two top-level compose files that orchestrate the app plus its core
dependencies (backend, frontend, 6-node Redis cluster, MongoDB, nginx
ingress), all host-networked to match infra/. Coturn/Envoy/HAProxy stay
in infra/.

- backend/Dockerfile: multi-stage (dev via ts-node/nodemon; prod compiles
  TS -> dist and runs node). Add build/start scripts; build copies
  src/scripts (rateLimit.lua) into dist since tsc does not emit it.
- frontend/Dockerfile: multi-stage (dev Vite server; prod nginx serving
  the SPA and proxying /backend + /socket.io -> :3000 as the ingress).
- docker-compose.local.yml: hot-reload dev with bind-mounted source.
- docker-compose.prod.yml: optimized images, restart policies, Mongo in
  a named volume, VITE_* baked in at build time.
- deploy/: local nginx ingress conf, idempotent Redis cluster init,
  env.docker.template documenting all required vars.
- README.docker.md: run instructions and caveats.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Aug 27, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: 902fcacf-41cc-463d-8356-7b510b5ffee3


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

11 issues found across 20 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="frontend/src/router/index.tsx">

<violation number="1" location="frontend/src/router/index.tsx:19">
P1: These routes now bypass `ProtectedRoute`, allowing unauthenticated users to open the dashboard, broadcaster, and viewer pages. Keep these routes nested under `ProtectedRoute` so the existing authentication and socket readiness checks still run.</violation>
</file>

<file name="frontend/nginx.conf">

<violation number="1" location="frontend/nginx.conf:18">
P1: When production sets `HOST_PUBLIC_IP` as documented, the backend binds to that public address rather than `127.0.0.1`, so this proxy target refuses the API requests and the analogous Socket.IO target fails too. Bind the backend listener to loopback/all interfaces independently of the advertised public IP, or make both ingress targets use the backend's actual bind address.</violation>
</file>

<file name="load-test/sfu-capacity.js">

<violation number="1" location="load-test/sfu-capacity.js:24">
P2: A non-positive `--batchSize` makes the main loop non-terminating. Reject non-positive and non-finite parsed values before entering the viewer loop.</violation>

<violation number="2" location="load-test/sfu-capacity.js:678">
P2: When `maxViewers` is not divisible by `batchSize`, the test silently measures fewer viewers than requested. Continue until `MAX_VIEWERS` and size the final batch to the remaining viewer count.</violation>
</file>

<file name="deploy/redis/init-cluster.sh">

<violation number="1" location="deploy/redis/init-cluster.sh:21">
P2: When a persisted topology has slot coverage but is missing nodes or replicas, this check exits successfully without creating or repairing the promised 3-master/3-replica cluster. Validate `cluster nodes` or `cluster slots` and require six connected nodes, three masters, and three replicas before returning.</violation>
</file>

<file name="docker-compose.local.yml">

<violation number="1" location="docker-compose.local.yml:129">
P2: The `- /app/node_modules` anonymous volumes are created once and reused across `up` runs, so after you add a dependency and `docker compose up --build`, the rebuilt image's node_modules (baked by the dev-stage `npm install`) stays hidden behind the stale volume. Docker only seeds that volume from the image on its first creation. The usual fix, `docker compose down -v`, also wipes the named Mongo/Redis data volumes. Name the volumes (e.g. a `backend-node-modules` named volume managed per-service, or document the `down -v`/`volume rm` step) so dependency changes take effect without nuking database data.</violation>
</file>

<file name="load-test/sfu-capacity-results.csv">

<violation number="1" location="load-test/sfu-capacity-results.csv:2">
P2: Every one of the 100 rows has joinP50 == joinP99 and firstFrameP50 == firstFrameP99 (identical value). The generator records one row per batch and computes percentile() over only that batch's samples, so the committed file was produced with batchSize=1 where each batch holds a single sample and percentile() degenerates to that one value for both P50 and P99. As a committed capacity-test artifact the P50/P99 columns therefore do not represent real percentiles or a latency distribution — each value is just one viewer's latency — which misrepresents the SFU capacity the file claims to document. Re-run with a real batch size (e.g., 5-10) so the percentiles are meaningful, or drop/relabel these columns and state the per-row sample size.</violation>
</file>

<file name="deploy/nginx/local.conf">

<violation number="1" location="deploy/nginx/local.conf:14">
P2: The `location /` and `location /socket.io/` blocks force `Connection: upgrade` on every request, including plain HTTP and Socket.IO long-polling requests that never send an `Upgrade` header. This is the classic nginx WebSocket anti-pattern: nginx can no longer reuse the upstream keep-alive connection, so it opens a new TCP connection to Vite/backend for each request and emits a bogus `Connection: upgrade` header on non-upgrade traffic. Use a `map` so upgrade is only negotiated when the client actually requests it.</violation>
</file>

<file name="backend/package.json">

<violation number="1" location="backend/package.json:8">
P3: Running `npm run build` twice nests the copy: once `dist/scripts` exists, `cp -r src/scripts dist/scripts` creates `dist/scripts/scripts/rateLimit.lua` instead of refreshing the file in place. The runtime still reads `dist/scripts/rateLimit.lua` so nothing breaks functionally, but the build is not idempotent and accumulates stale nested copies with each rebuild. Copy the directory contents instead, e.g. `mkdir -p dist/scripts && cp -r src/scripts/. dist/scripts/`.</violation>
</file>

<file name="docker-compose.prod.yml">

<violation number="1" location="docker-compose.prod.yml:108">
P2: `redis-init` is a one-shot job with `restart: "no"`, and the backend starts only after it exits successfully (`depends_on ... service_completed_successfully`). If cluster creation fails transiently — a Redis node crashes or the `--cluster create` against an inconsistent state errors — the init container exits non-zero and nothing retries it, so the backend (and the whole stack) stays down until someone manually runs `docker compose restart redis-init`. Give the init a bounded retry policy (e.g. `restart: on-failure`), or have the backend not hard-gate on it, so a transient init failure self-heals.</violation>
</file>

<file name="frontend/Dockerfile">

<violation number="1" location="frontend/Dockerfile:24">
P2: VITE_TURN_USERNAME and VITE_TURN_CREDENTIAL build args are dead config: the frontend never reads them (src/utils/iceServer.util.ts obtains username/credential at runtime from the backend GET /turn/credentials). A real TURN secret passed here would be copied into the build-stage image layer and is reachable via `docker history`, yet serve no purpose. Remove these two ARG/ENV lines (and the matching args in docker-compose.prod.yml) so a shared secret can't be baked into the build.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

<Route path="/signin" element={<SignInPage />} />
<Route path="/signup" element={<SignUpPage />} />

<Route path="/broadcaster" element={<BroadcasterPage />} />

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: These routes now bypass ProtectedRoute, allowing unauthenticated users to open the dashboard, broadcaster, and viewer pages. Keep these routes nested under ProtectedRoute so the existing authentication and socket readiness checks still run.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/router/index.tsx, line 19:

<comment>These routes now bypass `ProtectedRoute`, allowing unauthenticated users to open the dashboard, broadcaster, and viewer pages. Keep these routes nested under `ProtectedRoute` so the existing authentication and socket readiness checks still run.</comment>

<file context>
@@ -16,10 +16,10 @@ export default function Router() {
         <Route path="/signin" element={<SignInPage />} />
         <Route path="/signup" element={<SignUpPage />} />
 
+        <Route path="/broadcaster" element={<BroadcasterPage />} />
+        <Route path="/viewer" element={<ViewerPage />} />
+        <Route path="/dashboard" element={<DashboardPage />} />
</file context>

Comment thread backend/Dockerfile Outdated
Comment thread frontend/nginx.conf
# REST API. Trailing slash strips the /backend/ prefix:
# /backend/api/v1/... -> /api/v1/... ; /backend/db/__ping -> /db/__ping
location /backend/ {
proxy_pass http://127.0.0.1:3000/;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: When production sets HOST_PUBLIC_IP as documented, the backend binds to that public address rather than 127.0.0.1, so this proxy target refuses the API requests and the analogous Socket.IO target fails too. Bind the backend listener to loopback/all interfaces independently of the advertised public IP, or make both ingress targets use the backend's actual bind address.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/nginx.conf, line 18:

<comment>When production sets `HOST_PUBLIC_IP` as documented, the backend binds to that public address rather than `127.0.0.1`, so this proxy target refuses the API requests and the analogous Socket.IO target fails too. Bind the backend listener to loopback/all interfaces independently of the advertised public IP, or make both ingress targets use the backend's actual bind address.</comment>

<file context>
@@ -0,0 +1,38 @@
+    # REST API. Trailing slash strips the /backend/ prefix:
+    #   /backend/api/v1/... -> /api/v1/... ; /backend/db/__ping -> /db/__ping
+    location /backend/ {
+        proxy_pass http://127.0.0.1:3000/;
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
</file context>

Comment thread load-test/sfu-capacity.js Outdated
Comment thread backend/src/utils/socket.util.ts
Comment thread docker-compose.local.yml
RECORDING_IP: "127.0.0.1"
volumes:
- ./backend:/app
- /app/node_modules

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The - /app/node_modules anonymous volumes are created once and reused across up runs, so after you add a dependency and docker compose up --build, the rebuilt image's node_modules (baked by the dev-stage npm install) stays hidden behind the stale volume. Docker only seeds that volume from the image on its first creation. The usual fix, docker compose down -v, also wipes the named Mongo/Redis data volumes. Name the volumes (e.g. a backend-node-modules named volume managed per-service, or document the down -v/volume rm step) so dependency changes take effect without nuking database data.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docker-compose.local.yml, line 129:

<comment>The `- /app/node_modules` anonymous volumes are created once and reused across `up` runs, so after you add a dependency and `docker compose up --build`, the rebuilt image's node_modules (baked by the dev-stage `npm install`) stays hidden behind the stale volume. Docker only seeds that volume from the image on its first creation. The usual fix, `docker compose down -v`, also wipes the named Mongo/Redis data volumes. Name the volumes (e.g. a `backend-node-modules` named volume managed per-service, or document the `down -v`/`volume rm` step) so dependency changes take effect without nuking database data.</comment>

<file context>
@@ -0,0 +1,166 @@
+      RECORDING_IP: "127.0.0.1"
+    volumes:
+      - ./backend:/app
+      - /app/node_modules
+    depends_on:
+      mongo:
</file context>

Comment thread docker-compose.prod.yml
volumes:
- ./deploy/redis/init-cluster.sh:/init-cluster.sh:ro
entrypoint: ["sh", "/init-cluster.sh"]
restart: "no"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: redis-init is a one-shot job with restart: "no", and the backend starts only after it exits successfully (depends_on ... service_completed_successfully). If cluster creation fails transiently — a Redis node crashes or the --cluster create against an inconsistent state errors — the init container exits non-zero and nothing retries it, so the backend (and the whole stack) stays down until someone manually runs docker compose restart redis-init. Give the init a bounded retry policy (e.g. restart: on-failure), or have the backend not hard-gate on it, so a transient init failure self-heals.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docker-compose.prod.yml, line 108:

<comment>`redis-init` is a one-shot job with `restart: "no"`, and the backend starts only after it exits successfully (`depends_on ... service_completed_successfully`). If cluster creation fails transiently — a Redis node crashes or the `--cluster create` against an inconsistent state errors — the init container exits non-zero and nothing retries it, so the backend (and the whole stack) stays down until someone manually runs `docker compose restart redis-init`. Give the init a bounded retry policy (e.g. `restart: on-failure`), or have the backend not hard-gate on it, so a transient init failure self-heals.</comment>

<file context>
@@ -0,0 +1,166 @@
+    volumes:
+      - ./deploy/redis/init-cluster.sh:/init-cluster.sh:ro
+    entrypoint: ["sh", "/init-cluster.sh"]
+    restart: "no"
+
+  backend:
</file context>

Comment thread frontend/Dockerfile
WORKDIR /app
ARG VITE_API_URL=
ARG VITE_SIGNALING_URL=
ARG VITE_TURN_USERNAME=

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: VITE_TURN_USERNAME and VITE_TURN_CREDENTIAL build args are dead config: the frontend never reads them (src/utils/iceServer.util.ts obtains username/credential at runtime from the backend GET /turn/credentials). A real TURN secret passed here would be copied into the build-stage image layer and is reachable via docker history, yet serve no purpose. Remove these two ARG/ENV lines (and the matching args in docker-compose.prod.yml) so a shared secret can't be baked into the build.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/Dockerfile, line 24:

<comment>VITE_TURN_USERNAME and VITE_TURN_CREDENTIAL build args are dead config: the frontend never reads them (src/utils/iceServer.util.ts obtains username/credential at runtime from the backend GET /turn/credentials). A real TURN secret passed here would be copied into the build-stage image layer and is reachable via `docker history`, yet serve no purpose. Remove these two ARG/ENV lines (and the matching args in docker-compose.prod.yml) so a shared secret can't be baked into the build.</comment>

<file context>
@@ -1,18 +1,46 @@
+WORKDIR /app
+ARG VITE_API_URL=
+ARG VITE_SIGNALING_URL=
+ARG VITE_TURN_USERNAME=
+ARG VITE_TURN_CREDENTIAL=
+ARG VITE_TURN_UDP_URL=
</file context>

Comment thread README.docker.md Outdated
Comment thread backend/package.json
"scripts": {
"dev": "nodemon --watch src --ext ts,d.ts --exec ts-node --files src/index.ts"
"dev": "nodemon --watch src --ext ts,d.ts --exec ts-node --files src/index.ts",
"build": "tsc -p tsconfig.json && cp -r src/scripts dist/scripts",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Running npm run build twice nests the copy: once dist/scripts exists, cp -r src/scripts dist/scripts creates dist/scripts/scripts/rateLimit.lua instead of refreshing the file in place. The runtime still reads dist/scripts/rateLimit.lua so nothing breaks functionally, but the build is not idempotent and accumulates stale nested copies with each rebuild. Copy the directory contents instead, e.g. mkdir -p dist/scripts && cp -r src/scripts/. dist/scripts/.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/package.json, line 8:

<comment>Running `npm run build` twice nests the copy: once `dist/scripts` exists, `cp -r src/scripts dist/scripts` creates `dist/scripts/scripts/rateLimit.lua` instead of refreshing the file in place. The runtime still reads `dist/scripts/rateLimit.lua` so nothing breaks functionally, but the build is not idempotent and accumulates stale nested copies with each rebuild. Copy the directory contents instead, e.g. `mkdir -p dist/scripts && cp -r src/scripts/. dist/scripts/`.</comment>

<file context>
@@ -4,7 +4,9 @@
   "scripts": {
-    "dev": "nodemon --watch src --ext ts,d.ts --exec ts-node --files src/index.ts"
+    "dev": "nodemon --watch src --ext ts,d.ts --exec ts-node --files src/index.ts",
+    "build": "tsc -p tsconfig.json && cp -r src/scripts dist/scripts",
+    "start": "node dist/index.js"
   },
</file context>

Harxhit and others added 6 commits September 7, 2026 14:52
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
@sonarqubecloud

sonarqubecloud Bot commented Sep 7, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
C Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant