Inside the container, the public URL points at nothing
Whet's backend delivered internal webhooks by dialing the app's public URL. Inside the container that origin resolved back to the backend itself and events vanished. Give server-to-server traffic its own base URL — the browser origin often isn't routable from inside.
Whet’s backend delivered internal webhooks by dialing the app’s public
URL. Inside the container network that origin resolved back to the
backend itself, so the events never reached the web app and the live
SSE bar never received scrape updates. The fix (6858d8d) introduced
INTERNAL_APP_URL, a dedicated base URL for server-to-server calls,
kept separate from the browser-facing PUBLIC_APP_URL.
One URL, two audiences
A public origin is a browser affordance. It has to be resolvable from
the outside — DNS, TLS, the load balancer, the whole path a user’s
request takes to reach the app. None of that is available, or even
correct, from a peer process one hop away on the container network.
Reusing PUBLIC_APP_URL for a backend-to-web call quietly assumed the
two audiences share one address. They don’t. The address a browser
needs and the address a sibling service needs are separate facts that
happened to look like one string.
It breaks silently, which is the worst kind
Nothing threw. The webhook fired, the request went somewhere, the job reported no error. What actually happened was the backend dialing its own public origin and landing back on itself — the events simply never arrived, and the SSE bar sat empty with no exception to trace. A misroute that resolves to the wrong service is harder to find than one that fails outright: there’s no stack trace, only an absence. The symptom lives downstream of the cause, in a UI that shows nothing.
Make the second address explicit
The correction isn’t a smarter lookup or a runtime probe of what’s
reachable. It’s a second variable. Server-to-server routes read
INTERNAL_APP_URL; browser-facing code keeps PUBLIC_APP_URL. Once
the two are named apart, the deploy config states plainly which hop
uses which address, and there’s no code path left that guesses. The
change was flagged an ADR-candidate for exactly that reason — it’s a
boundary decision, not a patch. Any service that talks to a peer by
its public origin carries the same latent bug, dormant until the day
the network topology stops making the two addresses coincide.