diff --git a/SELF_HOSTING_ADVANCED.md b/SELF_HOSTING_ADVANCED.md index fc94ab97d7..ea5b5fe6bc 100644 --- a/SELF_HOSTING_ADVANCED.md +++ b/SELF_HOSTING_ADVANCED.md @@ -139,9 +139,11 @@ If the frontend and backend are served from different hostnames, `COOKIE_DOMAIN` | Variable | Default | Description | |----------|---------|-------------| | `PORT` | `8080` | Backend port — the one to edit. It is the port the backend process listens on for a local/bare run, and the host port the Compose self-host stack publishes. In Compose the container always listens on `8080` internally, so changing this needs no rebuild. | -| `BACKEND_PORT` | Value of `PORT` | Optional alias that overrides `PORT` for the backend. `API_PORT` and `SERVER_PORT` are further aliases; the **alias order** is `BACKEND_PORT` → `API_PORT` → `SERVER_PORT` → `PORT` → `8080`, and it is the same in `Makefile`, `scripts/local-env.sh`, `docker-compose.selfhost.yml` and the web dev fallback. Leave them unset unless the host port must differ from the port the process listens on. | +| `BACKEND_PORT` | Value of `PORT` | Optional alias that overrides `PORT` for the backend. `API_PORT` and `SERVER_PORT` are further aliases; the **alias order** is `BACKEND_PORT` → `API_PORT` → `SERVER_PORT` → `PORT` → `8080`, and it is the same in `Makefile`, `scripts/local-env.sh` and `docker-compose.selfhost.yml`. Leave them unset unless the host port must differ from the port the process listens on. | | `METRICS_ADDR` | empty | Optional Prometheus metrics listener, for example `127.0.0.1:9090` | | `FRONTEND_PORT` | `3000` | Frontend port. Host port in Compose; the container always listens on `3000` internally. | +| `CORS_ALLOWED_ORIGINS` | Value of `FRONTEND_ORIGIN` | Comma-separated list of allowed origins. Governs **both** the HTTP CORS allowlist **and** the WebSocket `Origin` check. A browser origin that isn't listed here (and isn't `localhost`) has its real-time WebSocket upgrade rejected with `403`, so live updates stop working until a manual refresh. | +| `LOG_LEVEL` | `info` | Log level: `debug`, `info`, `warn`, `error` | > **Which source wins depends on the entry point**, and only the alias order above > is shared. Docker Compose lets the calling environment outrank `.env` @@ -153,8 +155,12 @@ If the frontend and backend are served from different hostnames, `COOKIE_DOMAIN` > `make selfhost` and both installers read the published port back from > `docker compose port`, so the health check and the printed URL always match > what Compose actually published. -| `CORS_ALLOWED_ORIGINS` | Value of `FRONTEND_ORIGIN` | Comma-separated list of allowed origins. Governs **both** the HTTP CORS allowlist **and** the WebSocket `Origin` check. A browser origin that isn't listed here (and isn't `localhost`) has its real-time WebSocket upgrade rejected with `403`, so live updates stop working until a manual refresh. | -| `LOG_LEVEL` | `info` | Log level: `debug`, `info`, `warn`, `error` | + +The web development server is one intentional exception to the generic `PORT` +fallback: Next uses `PORT` for its own frontend listener before it evaluates the +rewrite configuration. Its backend fallback therefore accepts +`BACKEND_PORT` → `API_PORT` → `SERVER_PORT` → `8080`, while an explicit +`REMOTE_API_URL` or `NEXT_PUBLIC_API_URL` still takes priority. ### CLI / Daemon diff --git a/apps/web/config/runtime-urls.test.ts b/apps/web/config/runtime-urls.test.ts index 7af7219b76..f1be2809b2 100644 --- a/apps/web/config/runtime-urls.test.ts +++ b/apps/web/config/runtime-urls.test.ts @@ -197,18 +197,18 @@ describe("dev-only fallbacks", () => { ); }); - it("falls back through the backend port aliases to PORT", () => { - expect(resolveDevRemoteApiUrl({ PORT: "19081" })).toBe( - "http://localhost:19081", + it("ignores the frontend process PORT while honoring backend-specific aliases", () => { + expect(resolveDevRemoteApiUrl({ PORT: "3000" })).toBe( + "http://localhost:8080", ); - expect(resolveDevRemoteApiUrl({ API_PORT: "19082", PORT: "19081" })).toBe( + expect(resolveDevRemoteApiUrl({ API_PORT: "19082", PORT: "3000" })).toBe( "http://localhost:19082", ); expect( - resolveDevRemoteApiUrl({ SERVER_PORT: "19083", PORT: "19081" }), + resolveDevRemoteApiUrl({ SERVER_PORT: "19083", PORT: "3000" }), ).toBe("http://localhost:19083"); expect( - resolveDevRemoteApiUrl({ BACKEND_PORT: "19080", PORT: "19081" }), + resolveDevRemoteApiUrl({ BACKEND_PORT: "19080", PORT: "3000" }), ).toBe("http://localhost:19080"); }); diff --git a/apps/web/config/runtime-urls.ts b/apps/web/config/runtime-urls.ts index a6ba6a1720..24c7d97b27 100644 --- a/apps/web/config/runtime-urls.ts +++ b/apps/web/config/runtime-urls.ts @@ -44,15 +44,14 @@ export function resolveDocsUrl(env: RuntimeEnv): string | undefined { export function resolveDevRemoteApiUrl(env: RuntimeEnv): string { const configured = resolveRemoteApiUrl(env); if (configured) return configured; - // Same backend port chain as Makefile, scripts/local-env.sh and - // scripts/install.sh: PORT is the value to edit, the rest are aliases that - // override it. Reading only BACKEND_PORT pointed `pnpm dev` at 8080 for - // anyone who moved the backend by setting PORT. + // Next writes process.env.PORT with the frontend listener port before it + // evaluates next.config.ts. Treating that generic variable as a backend port + // would make every dev rewrite point back to the frontend itself. Only the + // backend-specific aliases are safe fallbacks in this process. const backendPort = env.BACKEND_PORT?.trim() || env.API_PORT?.trim() || env.SERVER_PORT?.trim() || - env.PORT?.trim() || "8080"; return `http://localhost:${backendPort}`; } diff --git a/docker-compose.selfhost.yml b/docker-compose.selfhost.yml index 755780dc1a..53306e30a0 100644 --- a/docker-compose.selfhost.yml +++ b/docker-compose.selfhost.yml @@ -19,8 +19,9 @@ # The published values above are HOST ports; the containers always listen on # 8080 / 3000 internally, so changing them never needs a rebuild. PORT is the # variable to edit; BACKEND_PORT, API_PORT and SERVER_PORT are optional aliases -# that override it in that order. Keep this alias order identical to Makefile, -# scripts/local-env.sh and apps/web/config/runtime-urls.ts. +# that override it in that order. Keep this alias order identical to Makefile +# and scripts/local-env.sh. The web dev fallback intentionally omits PORT +# because Next uses that variable for its own frontend listener. # # Note that *which source* wins differs per entry point — Compose lets the # calling environment outrank this file, while make lets the included env file