fix(selfhost): keep web dev proxy off frontend port

Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
Eve
2026-07-31 14:33:53 +08:00
parent 70c215f63c
commit 9863832fe7
4 changed files with 22 additions and 16 deletions

View File

@@ -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

View File

@@ -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");
});

View File

@@ -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}`;
}

View File

@@ -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