fix(gateway): advertise the host-published gateway port to agent containers - #435
fix(gateway): advertise the host-published gateway port to agent containers#435cfis wants to merge 2 commits into
Conversation
…ainers
Running two self-hosted stacks on one host (offset ports so they don't
collide) left agent containers unable to reach the gateway: both the proxy
URL handed to containers and the internal cache-invalidation call used a
port that ignored the operator's host-port remapping.
- Wire GATEWAY_BASE_URL (the host:port agents put in HTTPS_PROXY) to
ONECLI_GATEWAY_PORT in docker-compose, so it tracks the published port
instead of the hardcoded 10255 default.
- Add GATEWAY_SERVICE_URL: the URL the API uses to reach the gateway for
server-to-server calls (cache invalidation), pinned to the gateway's
fixed in-container port. Falls back to GATEWAY_API_URL, so single-stack
and remote-gateway ("cloud") deployments are unaffected.
- gateway-invalidate.ts uses GATEWAY_SERVICE_URL instead of GATEWAY_API_URL.
GATEWAY_API_URL keeps its meaning (URL advertised to outside callers). The
change is additive and opt-in — behavior only diverges when an operator
sets the new vars for an offset stack.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Greptile SummaryThis PR fixes two environment-variable mismatches that prevented agent containers in a port-offset self-hosted stack from reaching the gateway: the proxy URL advertised into agent containers (
Confidence Score: 4/5Safe to merge; the change is additive and the fallback in env.ts ensures existing single-stack and cloud deployments see no difference. The logic is sound and all existing deployment paths are protected by the GATEWAY_API_URL fallback. The only concern is that GATEWAY_SERVICE_URL in docker-compose.yml is a bare literal rather than a ${VAR:-default} substitution — the pattern every other configurable entry in that file follows — which silently prevents operators from overriding it via the .env file without a compose override file. docker/docker-compose.yml — the GATEWAY_SERVICE_URL literal vs. substitution pattern is worth a second look before merging.
|
| Filename | Overview |
|---|---|
| docker/docker-compose.yml | Adds GATEWAY_BASE_URL (using variable substitution) and GATEWAY_SERVICE_URL (hardcoded literal) to the onecli service; the literal breaks the compose file's consistent ${VAR:-default} override pattern. |
| packages/api/src/lib/env.ts | Adds GATEWAY_SERVICE_URL constant that falls back to GATEWAY_API_URL; well-documented and follows existing patterns. |
| packages/api/src/lib/gateway-invalidate.ts | Straightforward swap of GATEWAY_API_URL → GATEWAY_SERVICE_URL in both invalidation call sites; logic is unchanged. |
| packages/api/src/lib/env.test.ts | New test file covering both the fallback behaviour and the independent-override case for GATEWAY_SERVICE_URL; uses vi.stubEnv/resetModules correctly. |
| turbo.json | Adds GATEWAY_SERVICE_URL to globalPassThroughEnv so Turborepo propagates the variable correctly; minimal and correct. |
Sequence Diagram
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Agent as Agent Container
participant Host as Host (published port)
participant App as API Server (container)
participant GW as Gateway (container, :10255)
Note over Agent,GW: HTTPS_PROXY set via GATEWAY_BASE_URL
Agent->>Host: CONNECT host.docker.internal:ONECLI_GATEWAY_PORT
Host->>GW: forward to :10255 (port mapping)
GW-->>Agent: proxy tunnel established
Note over App,GW: Cache invalidation (server-to-server)
App->>GW: POST 127.0.0.1:10255/v1/cache/invalidate
Note right of App: GATEWAY_SERVICE_URL (fixed in-container port)
GW-->>App: 200 OK
Note over Agent,Host: SDK approval poller / browser
Agent->>Host: GET GATEWAY_API_URL/v1/...
Note right of Agent: GATEWAY_API_URL tracks published host port
Host->>GW: forward
GW-->>Agent: response
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Agent as Agent Container
participant Host as Host (published port)
participant App as API Server (container)
participant GW as Gateway (container, :10255)
Note over Agent,GW: HTTPS_PROXY set via GATEWAY_BASE_URL
Agent->>Host: CONNECT host.docker.internal:ONECLI_GATEWAY_PORT
Host->>GW: forward to :10255 (port mapping)
GW-->>Agent: proxy tunnel established
Note over App,GW: Cache invalidation (server-to-server)
App->>GW: POST 127.0.0.1:10255/v1/cache/invalidate
Note right of App: GATEWAY_SERVICE_URL (fixed in-container port)
GW-->>App: 200 OK
Note over Agent,Host: SDK approval poller / browser
Agent->>Host: GET GATEWAY_API_URL/v1/...
Note right of Agent: GATEWAY_API_URL tracks published host port
Host->>GW: forward
GW-->>Agent: response
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 1
docker/docker-compose.yml:50
`GATEWAY_SERVICE_URL` is the only value in the `environment` block that uses a bare literal instead of `${VAR:-default}` substitution. Every other configurable entry (`ONECLI_BIND_HOST`, `ONECLI_APP_PORT`, `ONECLI_GATEWAY_PORT`, etc.) follows the substitution pattern so operators can override via shell or the root `.env` file. With a literal, Docker Compose ignores any `GATEWAY_SERVICE_URL` an operator places in the `.env` file — the hardcoded string always wins — meaning the only escape hatch is a `docker-compose.override.yml`. Keeping the same substitution pattern here preserves the default behaviour (fixed in-container port) while letting advanced operators override without touching the compose file.
```suggestion
GATEWAY_SERVICE_URL: ${GATEWAY_SERVICE_URL:-http://127.0.0.1:10255}
```
Reviews (1): Last reviewed commit: "fix(gateway): advertise the host-publish..." | Re-trigger Greptile
| # (cache invalidation). The gateway always listens on 10255 inside this | ||
| # container regardless of host port remapping, so this stays fixed and is | ||
| # independent of ONECLI_GATEWAY_PORT. | ||
| GATEWAY_SERVICE_URL: http://127.0.0.1:10255 |
There was a problem hiding this comment.
GATEWAY_SERVICE_URL is the only value in the environment block that uses a bare literal instead of ${VAR:-default} substitution. Every other configurable entry (ONECLI_BIND_HOST, ONECLI_APP_PORT, ONECLI_GATEWAY_PORT, etc.) follows the substitution pattern so operators can override via shell or the root .env file. With a literal, Docker Compose ignores any GATEWAY_SERVICE_URL an operator places in the .env file — the hardcoded string always wins — meaning the only escape hatch is a docker-compose.override.yml. Keeping the same substitution pattern here preserves the default behaviour (fixed in-container port) while letting advanced operators override without touching the compose file.
| GATEWAY_SERVICE_URL: http://127.0.0.1:10255 | |
| GATEWAY_SERVICE_URL: ${GATEWAY_SERVICE_URL:-http://127.0.0.1:10255} |
Prompt To Fix With AI
This is a comment left during a code review.
Path: docker/docker-compose.yml
Line: 50
Comment:
`GATEWAY_SERVICE_URL` is the only value in the `environment` block that uses a bare literal instead of `${VAR:-default}` substitution. Every other configurable entry (`ONECLI_BIND_HOST`, `ONECLI_APP_PORT`, `ONECLI_GATEWAY_PORT`, etc.) follows the substitution pattern so operators can override via shell or the root `.env` file. With a literal, Docker Compose ignores any `GATEWAY_SERVICE_URL` an operator places in the `.env` file — the hardcoded string always wins — meaning the only escape hatch is a `docker-compose.override.yml`. Keeping the same substitution pattern here preserves the default behaviour (fixed in-container port) while letting advanced operators override without touching the compose file.
```suggestion
GATEWAY_SERVICE_URL: ${GATEWAY_SERVICE_URL:-http://127.0.0.1:10255}
```
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Builds on #227.
Problem
We run one OneCLI stack per user on a single macOS host — each agent in its own Colima Docker VM — with per-user offset ports so they don't collide on
127.0.0.1. #227 made the host ports configurable, but agents in a second, offset stack still can't reach the gateway: every credentialed call from a container dies at the proxy.Two values ignore the operator's port remapping:
GATEWAY_BASE_URL— thehost:portagents put inHTTPS_PROXY— defaults tohost.docker.internal:10255and is never set indocker-compose.yml, so agents dial:10255no matter whatONECLI_GATEWAY_PORTis.gateway-invalidate.tsusesGATEWAY_API_URL(the host-published port) for a call that actually runs app→gateway inside the container, where the gateway always listens on10255.Both are masked on the default stack (host port ==
10255) and under podman host-networking, where the two coincide. They only surface on macOS/Colima bridge networking with offset ports.Fix
Extends #227 to the values advertised into containers (which #227 deliberately left at the container-internal ports):
GATEWAY_BASE_URLtoONECLI_GATEWAY_PORTindocker-compose.yml.GATEWAY_SERVICE_URL— the app→gateway URL — pinned to the fixed in-container port, used bygateway-invalidate.ts. It falls back toGATEWAY_API_URL, so single-stack and remote/cloud deployments are unchanged.I don't love the name
GATEWAY_SERVICE_URL, I think it should really beGATEWAY_API_URLbut that is already taken. An alternative would be to renameGATEWAY_API_URLtoGATEWAY_PUBLIC_URLbut I figured that was probably a bridge too far. But let me know and I can update this PR.Testing
New
env.test.tscovers theGATEWAY_SERVICE_URLfallback/override;tscandeslintclean on the touched files.🤖 Generated with Claude Code