All notes
Architecture

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.