From 91b859746b854c916964cfe83c072a172fc0169c Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Sat, 23 May 2026 10:04:36 -0700 Subject: [PATCH] fix(docker): bake NEXT_PUBLIC_* values at build time, drop hardcoded api.bytelyst.com MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- docker-compose.override.yml | 10 ++++++++++ docker-compose.yml | 15 +++++++++++++-- web/Dockerfile | 16 ++++++++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 98e18fd..030f642 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -34,6 +34,16 @@ services: web: ports: !override - "3050:3045" + # NEXT_PUBLIC_* values are baked into the Next.js bundle at build + # time. They MUST be set as build args so `pnpm run build` inside + # the Dockerfile picks them up. Runtime `environment:` alone has no + # effect on the already-bundled client code. + build: + args: + NEXT_PUBLIC_NOTES_API_URL: "http://localhost:4016/api" + NEXT_PUBLIC_PLATFORM_SERVICE_URL: "http://localhost:4003/api" + NEXT_PUBLIC_EXTRACTION_SERVICE_URL: "http://localhost:4005" + NEXT_PUBLIC_MCP_SERVER_URL: "http://localhost:4007/api" environment: NEXT_PUBLIC_NOTES_API_URL: "http://localhost:4016/api" NEXT_PUBLIC_PLATFORM_SERVICE_URL: "http://localhost:4003/api" diff --git a/docker-compose.yml b/docker-compose.yml index 9a44223..0a5d94f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -46,15 +46,26 @@ services: context: . dockerfile: web/Dockerfile args: - NEXT_PUBLIC_NOTES_API_URL: https://api.bytelyst.com/notelett + # 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: https://api.bytelyst.com/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 diff --git a/web/Dockerfile b/web/Dockerfile index c4f481b..40b3f57 100644 --- a/web/Dockerfile +++ b/web/Dockerfile @@ -20,10 +20,26 @@ COPY web/next-env.d.ts ./next-env.d.ts COPY web/src/ ./src/ COPY shared/ ../shared/ +# NEXT_PUBLIC_* values are baked into the Next.js bundle at build time. +# Every URL the client uses must be declared here so docker-compose +# build args reach `pnpm run build`. The values are typically supplied +# from docker-compose.yml or .env. ARG NEXT_PUBLIC_NOTES_API_URL ARG NEXT_PUBLIC_PLATFORM_SERVICE_URL +ARG NEXT_PUBLIC_EXTRACTION_SERVICE_URL +ARG NEXT_PUBLIC_MCP_SERVER_URL +ARG NEXT_PUBLIC_DIAGNOSTICS_URL +ARG NEXT_PUBLIC_PRODUCT_NAME +ARG NEXT_PUBLIC_PRODUCT_ID +ARG NEXT_PUBLIC_TELEMETRY_TRANSPORT ENV NEXT_PUBLIC_NOTES_API_URL=$NEXT_PUBLIC_NOTES_API_URL ENV NEXT_PUBLIC_PLATFORM_SERVICE_URL=$NEXT_PUBLIC_PLATFORM_SERVICE_URL +ENV NEXT_PUBLIC_EXTRACTION_SERVICE_URL=$NEXT_PUBLIC_EXTRACTION_SERVICE_URL +ENV NEXT_PUBLIC_MCP_SERVER_URL=$NEXT_PUBLIC_MCP_SERVER_URL +ENV NEXT_PUBLIC_DIAGNOSTICS_URL=$NEXT_PUBLIC_DIAGNOSTICS_URL +ENV NEXT_PUBLIC_PRODUCT_NAME=$NEXT_PUBLIC_PRODUCT_NAME +ENV NEXT_PUBLIC_PRODUCT_ID=$NEXT_PUBLIC_PRODUCT_ID +ENV NEXT_PUBLIC_TELEMETRY_TRANSPORT=$NEXT_PUBLIC_TELEMETRY_TRANSPORT ENV NEXT_TELEMETRY_DISABLED=1 RUN pnpm run build