From 48ac4542557892e2f6fdf7d127ec5df9a1b1f037 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 20 Jun 2026 21:06:49 +0000 Subject: [PATCH] [bugfix] Frontend: give GQ_BACKEND_HOST a working default to fix 503s MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A remote single-host deployment that left GQ_BACKEND_HOST unset hit "api request failed: status=503" with Caddy logging "no upstreams available": the image's default for GQ_BACKEND_HOST was the empty string, so `reverse_proxy /_query* {$GQ_BACKEND_HOST}` expanded to a proxy with no upstream to dial. Give the proxy a sane fallback so it works out of the box: - Caddyfile: default the env placeholder to http://127.0.0.1:8146, so an unset OR empty GQ_BACKEND_HOST still yields a valid upstream. - Dockerfile: set the same value as the image-level ENV default instead of an empty string. - docker-compose.yaml: the frontend default pointed at :8145 (the goProbe sensor API port) while global-query listens on :8146 (SERVER_ADDR) — fix the mismatch so the documented stack proxies to the right service. - README: document GQ_BACKEND_HOST vs GQ_API_BASE_URL and the "no upstreams available" failure mode. Fixes #480 Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01TsqHDFCs6ebQPjXQNpvNsW --- docker-compose.yaml | 7 ++++--- frontend/goquery-ui/Dockerfile | 7 +++++-- frontend/goquery-ui/README.md | 29 ++++++++++++++++++++++++++++ frontend/goquery-ui/deploy/Caddyfile | 18 +++++++++++------ 4 files changed, 50 insertions(+), 11 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index f411120e..9a11fa9a 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -43,8 +43,9 @@ services: # from environment variables — no sidecar or init container needed. # # Environment variables: - # GQ_BACKEND_HOST - backend API address for Caddy's reverse proxy - # (default: http://127.0.0.1:8145) + # GQ_BACKEND_HOST - global-query API address for Caddy's reverse proxy. + # Must match the query service's SERVER_ADDR below + # (default: http://127.0.0.1:8146). # GQ_API_BASE_URL - URL the *browser* uses to reach the API. # Leave empty (default) to route via the Caddy proxy. # HOST_RESOLVER_TYPES - comma-separated resolver types shown in the UI (default: string) @@ -67,7 +68,7 @@ services: - frontend_data:/data - frontend_config:/config environment: - GQ_BACKEND_HOST: ${GQ_BACKEND_HOST:-http://127.0.0.1:8145} + GQ_BACKEND_HOST: ${GQ_BACKEND_HOST:-http://127.0.0.1:8146} GQ_API_BASE_URL: ${GQ_API_BASE_URL:-} HOST_RESOLVER_TYPES: ${HOST_RESOLVER_TYPES:-string} SSE_ON_LOAD: ${SSE_ON_LOAD:-true} diff --git a/frontend/goquery-ui/Dockerfile b/frontend/goquery-ui/Dockerfile index 3ed8b43b..f50f6d5a 100644 --- a/frontend/goquery-ui/Dockerfile +++ b/frontend/goquery-ui/Dockerfile @@ -59,11 +59,14 @@ USER caddy EXPOSE 5137 -# Runtime env vars with defaults (override in K8s Deployment / docker-compose) +# Runtime env vars with defaults (override in K8s Deployment / docker-compose). +# GQ_BACKEND_HOST defaults to the global-query address of the single-host +# docker-compose setup; the Caddyfile applies the same fallback when it is unset +# or empty, so the proxy always has an upstream to dial (see #480). ENV GQ_API_BASE_URL="" \ HOST_RESOLVER_TYPES="string" \ SSE_ON_LOAD="true" \ - GQ_BACKEND_HOST="" + GQ_BACKEND_HOST="http://127.0.0.1:8146" ENTRYPOINT ["/docker-entrypoint.sh"] CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile"] diff --git a/frontend/goquery-ui/README.md b/frontend/goquery-ui/README.md index 079b1555..67814882 100644 --- a/frontend/goquery-ui/README.md +++ b/frontend/goquery-ui/README.md @@ -58,6 +58,28 @@ make docker-up # run the hardened image (docker compose up --build) make docker-build # build Caddy image locally ``` +### Runtime configuration + +The image is configured entirely through environment variables, read at +container startup. The two networking knobs are easy to confuse: + +| Variable | Consumed by | Purpose | Default | +| --- | --- | --- | --- | +| `GQ_BACKEND_HOST` | Caddy (reverse proxy) | Where Caddy forwards `/_query*` and `/-/*` **server-side**. Point it at the global-query backend. | `http://127.0.0.1:8146` | +| `GQ_API_BASE_URL` | Browser (env.js) | URL the **browser** uses to reach the API. Leave empty to route through Caddy's same-origin proxy (recommended — keeps the CSP intact and avoids CORS). | empty | +| `HOST_RESOLVER_TYPES` | Browser (env.js) | Comma-separated resolver types offered in the UI. | `string` | +| `SSE_ON_LOAD` | Browser (env.js) | Enable SSE streaming by default. | `true` | + +Browser API calls go to `/_query*` on the same origin as the UI, and Caddy +proxies them on to `GQ_BACKEND_HOST` — so under normal use you only set +`GQ_BACKEND_HOST`, and leave `GQ_API_BASE_URL` empty. The default backend +(`http://127.0.0.1:8146`) matches the global-query address in the single-host +`docker-compose.yaml`; override it for other topologies, e.g. in Kubernetes: + +```bash +GQ_BACKEND_HOST=http://global-query.observability.svc.cluster.local:8145 +``` + ## Using the UI - Time range: quick presets (5m…30d) or set exact From/To. @@ -95,6 +117,13 @@ make docker-build # build Caddy image locally ## Troubleshooting +- API requests fail with `503` and Caddy logs `"no upstreams available"`: the + reverse proxy has no backend to dial because `GQ_BACKEND_HOST` resolved to an + empty value. Set it to your global-query address (see + [Runtime configuration](#runtime-configuration)). Note that listing the + variable with an empty value (e.g. `GQ_BACKEND_HOST=` or `${GQ_BACKEND_HOST:-}`) + overrides the built-in default with empty — leave it unset to take the default. + - If the editor reports a spurious import error for a newly added view file, run a fresh typecheck: ```bash diff --git a/frontend/goquery-ui/deploy/Caddyfile b/frontend/goquery-ui/deploy/Caddyfile index 82581eaf..4c65d610 100644 --- a/frontend/goquery-ui/deploy/Caddyfile +++ b/frontend/goquery-ui/deploy/Caddyfile @@ -43,19 +43,25 @@ } } - # Proxy API calls to the backend. The browser sees same-origin requests, - # so no CORS headers are required on the backend for browser traffic. - # GQ_BACKEND_HOST must be set in the container environment, e.g. - # GQ_BACKEND_HOST=http://global-query:8145 + # Proxy API calls to the global-query backend. The browser sees same-origin + # requests, so no CORS headers are required on the backend for browser traffic. + # + # GQ_BACKEND_HOST points Caddy at the backend. It falls back to + # http://127.0.0.1:8146 — the global-query address in the single-host + # docker-compose setup — when unset OR empty. Without a fallback, an empty + # value expands to a reverse_proxy with no upstream, and every request fails + # with 503 "no upstreams available" (see #480). Override it for any other + # topology, e.g. GQ_BACKEND_HOST=http://global-query.observability.svc:8145 + # in Kubernetes. # # header_up Host rewrites the Host header to the backend's hostport instead # of preserving the browser's external Host. This is required behind service # meshes like Istio, whose sidecar routes by Host: an unrecognized external # host falls through to PassthroughCluster and fails with 503 (see #478). - reverse_proxy /_query* {$GQ_BACKEND_HOST} { + reverse_proxy /_query* {$GQ_BACKEND_HOST:http://127.0.0.1:8146} { header_up Host {http.reverse_proxy.upstream.hostport} } - reverse_proxy /-/* {$GQ_BACKEND_HOST} { + reverse_proxy /-/* {$GQ_BACKEND_HOST:http://127.0.0.1:8146} { header_up Host {http.reverse_proxy.upstream.hostport} }