learning_ai_notes/docker-compose.yml
saravanakumardb1 91b859746b fix(docker): bake NEXT_PUBLIC_* values at build time, drop hardcoded api.bytelyst.com
Root cause: docker-compose.yml hardcoded NEXT_PUBLIC_NOTES_API_URL to
https://api.bytelyst.com/notelett — a production URL that doesn't
exist on this network — as the *build arg* for the web image. The
docker-compose.override.yml correctly set localhost:4016/api but only
on the runtime environment, which has no effect because NEXT_PUBLIC_*
values are baked into the Next.js bundle at build time (pnpm run build
inside the Dockerfile), not read at runtime.

Symptom: every authenticated client-side fetch from the deployed web
container went to https://api.bytelyst.com/notelett/... which the
corporate proxy intercepted with a blockpage. The saved-views client
in particular fired on every (app)/ layout mount, surfacing a
'Failed to fetch' toast on dashboard load. 4 release-flows.spec
tests failed because page.route('**/api/**') couldn't match the
api.bytelyst.com URLs at all.

Discovery: inspected the deployed bundle inside the running container.
'grep -oE "api.bytelyst.com" /app/web/web/.next/static/chunks/*.js'
returned multiple hits across the (app)/ layout, (auth)/ pages, and
share page. The string was absent from the source tree, which proved
it had been injected at build time via the broken arg default.

Discovery debug pattern (kept for future use):
  page.on('requestfailed', r => console.log(r.method(), r.url()));
  page.route('**/api/**', route => route.fulfill({status:200,body:'{}'}));
  await page.goto('/dashboard');
  // FAILED REQUESTS will list any URL not under /api/** that the SPA
  // attempted, exposing baked-in production URLs immediately.

Fix (three layers, defense in depth):

1. docker-compose.yml — replace hardcoded
   'NEXT_PUBLIC_NOTES_API_URL: https://api.bytelyst.com/notelett'
   in the build.args block with
   '${NEXT_PUBLIC_NOTES_API_URL:-http://localhost:4016/api}'.
   Same treatment for the runtime environment block. Add build args
   for the four other NEXT_PUBLIC_* values (extraction, MCP,
   diagnostics, product name/id, telemetry transport) so a single env
   var on the host controls both build and runtime layers.

2. web/Dockerfile — declare ARG and ENV lines for all seven
   NEXT_PUBLIC_* values so the build args reach 'pnpm run build'.
   Previously only NOTES_API_URL and PLATFORM_SERVICE_URL were
   declared, which meant overriding extraction/MCP/diagnostics via
   docker compose silently had no effect on the bundle.

3. docker-compose.override.yml — add a build.args block mirroring the
   four URL overrides so the local-only override also reaches build
   time, not just runtime. Comment block explains the bake-time vs
   runtime distinction so future contributors don't repeat the bug.

Verified end-to-end after the fix:
  - docker compose build --no-cache web + up -d → grep of bundle now
    shows 'localhost:4016/api', api.bytelyst.com fully gone.
  - Debug interception test: zero requestfailed events on /dashboard.
  - Playwright release-flows.spec.ts: 4 failed → 4 passed (after URL
    fix; no test code changed for these four tests).
  - Full Playwright suite (--ignore-snapshots): 43 passed.
  - scripts/e2e-docker-test.sh: 9/9 backend API lifecycle steps pass.
  - pnpm run verify: backend 380/380, web 96/96, mobile 97/97.
2026-05-23 10:04:36 -07:00

78 lines
3.6 KiB
YAML

services:
backend:
container_name: notelett-backend
build:
context: .
dockerfile: backend/Dockerfile
ports:
- "4016:4016"
environment:
NODE_ENV: development
PORT: 4016
HOST: 0.0.0.0
PRODUCT_ID: notelett
SERVICE_NAME: notelett-backend
JWT_SECRET: ${JWT_SECRET:-dev-secret-change-me-at-least-32-characters}
COSMOS_ENDPOINT: ${COSMOS_ENDPOINT:-}
COSMOS_KEY: ${COSMOS_KEY:-}
COSMOS_DATABASE: ${COSMOS_DATABASE:-bytelyst}
DB_PROVIDER: ${DB_PROVIDER:-memory}
CORS_ORIGIN: ${CORS_ORIGIN:-http://localhost:3000}
PLATFORM_SERVICE_URL: ${PLATFORM_SERVICE_URL:-http://localhost:4003}
EXTRACTION_SERVICE_URL: ${EXTRACTION_SERVICE_URL:-http://localhost:4005}
MCP_SERVER_URL: ${MCP_SERVER_URL:-http://localhost:4007}
TELEMETRY_ENABLED: ${TELEMETRY_ENABLED:-false}
FEATURE_FLAGS_ENABLED: ${FEATURE_FLAGS_ENABLED:-false}
FIELD_ENCRYPT_ENABLED: ${FIELD_ENCRYPT_ENABLED:-false}
FIELD_ENCRYPT_KEY_PROVIDER: ${FIELD_ENCRYPT_KEY_PROVIDER:-memory}
LLM_PROVIDER: ${LLM_PROVIDER:-mock}
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
OPENAI_BASE_URL: ${OPENAI_BASE_URL:-}
AZURE_OPENAI_ENDPOINT: ${AZURE_OPENAI_ENDPOINT:-}
AZURE_OPENAI_API_KEY: ${AZURE_OPENAI_API_KEY:-}
LLM_DEFAULT_MODEL: ${LLM_DEFAULT_MODEL:-gpt-4o-mini}
LLM_VISION_MODEL: ${LLM_VISION_MODEL:-gpt-4o}
LLM_EMBEDDING_MODEL: ${LLM_EMBEDDING_MODEL:-text-embedding-3-small}
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "node -e \"fetch('http://localhost:4016/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\""]
interval: 30s
timeout: 5s
retries: 3
web:
container_name: notelett-web
build:
context: .
dockerfile: web/Dockerfile
args:
# NEXT_PUBLIC_* values are baked into the Next.js bundle at build
# time, so they MUST be passed as build args, not just runtime
# environment. The defaults below target a local stack; override
# via the same-named env var on the host (the value is captured
# by docker compose's ${VAR:-default} substitution).
NEXT_PUBLIC_NOTES_API_URL: ${NEXT_PUBLIC_NOTES_API_URL:-http://localhost:4016/api}
NEXT_PUBLIC_PLATFORM_SERVICE_URL: ${NEXT_PUBLIC_PLATFORM_SERVICE_URL:-http://localhost:4003/api}
NEXT_PUBLIC_EXTRACTION_SERVICE_URL: ${NEXT_PUBLIC_EXTRACTION_SERVICE_URL:-http://localhost:4005}
NEXT_PUBLIC_MCP_SERVER_URL: ${NEXT_PUBLIC_MCP_SERVER_URL:-http://localhost:4007/api}
NEXT_PUBLIC_DIAGNOSTICS_URL: ${NEXT_PUBLIC_DIAGNOSTICS_URL:-http://localhost:3000}
NEXT_PUBLIC_PRODUCT_NAME: ${NEXT_PUBLIC_PRODUCT_NAME:-NoteLett}
NEXT_PUBLIC_PRODUCT_ID: ${NEXT_PUBLIC_PRODUCT_ID:-notelett}
NEXT_PUBLIC_TELEMETRY_TRANSPORT: ${NEXT_PUBLIC_TELEMETRY_TRANSPORT:-fetch}
ports:
- "3000:3045"
environment:
NODE_ENV: production
NEXT_PUBLIC_PRODUCT_NAME: NoteLett
NEXT_PUBLIC_PRODUCT_ID: notelett
NEXT_PUBLIC_NOTES_API_URL: ${NEXT_PUBLIC_NOTES_API_URL:-http://localhost:4016/api}
NEXT_PUBLIC_PLATFORM_SERVICE_URL: ${NEXT_PUBLIC_PLATFORM_SERVICE_URL:-http://localhost:4003/api}
NEXT_PUBLIC_EXTRACTION_SERVICE_URL: ${EXTRACTION_SERVICE_URL:-http://localhost:4005}
NEXT_PUBLIC_MCP_SERVER_URL: ${MCP_SERVER_URL:-http://localhost:4007}/api
NEXT_PUBLIC_DIAGNOSTICS_URL: ${DIAGNOSTICS_URL:-http://localhost:3000}
NEXT_PUBLIC_TELEMETRY_TRANSPORT: fetch
depends_on:
backend:
condition: service_healthy
restart: unless-stopped