ADR-0007: Frontend public configuration — runtime first, never a baked localhost
- Status
- accepted (revised: originally "baked at image build time")
- Decided
- August–September 2026; revised when release images became domain-independent
- Deciders
- Platform Engineering
- Motivating failures
- In the original hosted deployment: a sign-in outage caused by a tracked
.env.production.localthat baked the wrong origins; "Cookie not found" on signup because interim hostnames were baked before the final domain went live; a feature flag that was never passed to the build
Context
web/ and web-admin/ are Next.js apps. Next.js inlines
NEXT_PUBLIC_* values into the client bundle when they are present during
next build. Originally every origin (API, Keycloak, Code Intel, App Builder) and
feature switch was a build argument, so each environment needed its own image, and an
omitted argument silently baked a localhost default that broke sign-in on any real domain.
Published release images must instead work on any domain.
Decision
- Public settings are read at runtime. Each app's
app/lib/env.tsholds an allow-list of public keys; the root layout renderswindow.__ENVfrom the server's environment on each request, and every read goes throughgetPublicEnv(key). - Precedence: runtime environment, then a value baked at build time, then the code's local-development default. An empty string counts as unset.
- Build arguments stay optional and empty by default in
web/Dockerfileandweb-admin/Dockerfile. The Dockerfile comment states the rule: never default them to a localhost URL. - Compose and Helm supply the values at runtime from
PUBLIC_*(Compose) or chart values (Helm).
Alternatives considered
| Option | Outcome |
|---|---|
| Bake everything at build time (the original rule) | Replaced: one image per environment, and a missed argument fails silently. |
A separate /env.js route | Rejected in favour of an inline script in the already-dynamic root layout: no extra blocking request (web/docs/RUNTIME-CONFIG.md). |
Consequences
Positive
- One published image works for localhost, a Compose domain deployment and Kubernetes.
- Changing a domain or feature flag is a restart with new environment, not a rebuild.
Negative / costs
- A value that is baked at build time cannot be blanked at runtime (empty means unset). Do not bake environment-specific values into images you share.
- Local dotenv files (
.env*.local) inweb/orweb-admin/are read bynext build; build images from a clean checkout.
Standing rule
No browser-facing URL may be localhost/127.0.0.1 on a server. Set the
PUBLIC_* values (Compose) or chart values (Helm) to the real public origins; scripts/vegaduta doctor checks this.
Evidence & references
web/docs/RUNTIME-CONFIG.md;web/app/lib/env.ts.web/Dockerfile,web-admin/Dockerfile— optionalARGs, empty by default.deploy/compose/docker-compose.yml— web/web-admin runtime environment;deploy/helm/README.md.