feat(config): runtime config + nginx /api proxy for k8s deploy#94
Merged
Conversation
Make the SPA image deployable across environments without rebuilds.
The released 0.1.0 image baked VITE_KEYCLOAK_* and VITE_API_ENDPOINT
at build time, which meant the published bundle couldn't talk to a
backend or Keycloak from k8s without per-environment rebuilds.
Two changes, one motivation:
1. Backend API moves from a build-time-baked URL to a relative
/api/ path proxied by the SPA pod's own nginx. mutator.ts now
always returns /api; nginx.conf adds a location /api/ that
proxy_passes to ${BACKEND_HOST} via a variable + resolver, so
the container starts even if the backend isn't reachable yet
and resolution happens at request time. BACKEND_HOST defaults
to smartem-http-api-service (the k8s service name); k8s
Deployments override via env, plain docker via -e.
2. Keycloak URL/realm/clientId and authEnabled move from build-
time VITE_KEYCLOAK_* env vars to a runtime /config.json
fetched at boot. main.tsx awaits the fetch, calls
setRuntimeConfig(), then renders AuthGate; auth/config.ts
exposes getKeycloakConfig() and isAuthEnabled() backed by the
loaded config (throws if read before load). The image ships
a placeholder config.json with dev-mock defaults; k8s
ConfigMap mounts the per-environment file at
/usr/share/nginx/html/config.json.
Other changes:
- Dockerfile: copy nginx.conf as a template under
/etc/nginx/templates/ so the nginx:alpine entrypoint runs
envsubst on it. NGINX_ENTRYPOINT_LOCAL_RESOLVERS=1 makes the
image's 15-local-resolvers.envsh export the pod's resolver(s)
into NGINX_LOCAL_RESOLVERS, and NGINX_ENVSUBST_FILTER scopes
envsubst to BACKEND_HOST and NGINX_LOCAL_RESOLVERS only.
- .env.example: drop the now-runtime VITE_KEYCLOAK_*/
VITE_AUTH_ENABLED; document where to edit them for local dev.
- Version bump to 0.2.0 in both apps/smartem/package.json and
apps/smartem/src/version.ts.
The vite dev server's existing /api proxy keeps working for
npm run dev:smartem; /config.json is served from public/.
Verified locally:
- docker build succeeds end-to-end
- container starts with default unreachable BACKEND_HOST
- GET / serves the SPA, /version returns the build JSON,
/config.json returns the placeholder
- mounting an alternate /config.json at runtime overrides the
placeholder (k8s ConfigMap pattern)
- /api/ proxies through nginx to a stand-in backend on a
shared docker network
- SPA history-mode fallback still works for client routes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase A of the smartem-frontend k8s deploy work (see prior PRs #74, #90, #91, #92, #93). Makes the SPA image deployable across environments without per-env rebuilds.
The released
0.1.0image bakedVITE_KEYCLOAK_*andVITE_API_ENDPOINTat build time, which meant the published bundle couldn't talk to a backend or Keycloak from k8s without rebuilding per environment. Two coupled changes solve both:Backend API moves to relative
/api/proxied by nginx.mutator.tsnow always returns/api. The Dockerfile's nginx stage addslocation /api/thatproxy_passes to${BACKEND_HOST}via a variable +resolver, so the container starts even if the backend isn't ready yet and DNS resolution happens at request time.BACKEND_HOSTdefaults tosmartem-http-api-service(the k8s service name). The vite dev server's existing/apiproxy keeps working unchanged.Keycloak URL/realm/clientId and
authEnabledmove to runtime/config.json.main.tsxfetches/config.jsonbefore render, callssetRuntimeConfig(), then mountsAuthGate.auth/config.tsexposesgetKeycloakConfig()andisAuthEnabled()backed by the loaded config (throws if read before load). The image ships a placeholderapps/smartem/public/config.jsonwith dev defaults (Keycloak mock atlocalhost:30090,authEnabled: false); k8s ConfigMap mounts the per-environment file at/usr/share/nginx/html/config.json.Other touches:
nginx.confas a template under/etc/nginx/templates/so thenginx:alpineentrypoint runsenvsubston it.NGINX_ENTRYPOINT_LOCAL_RESOLVERS=1makes the image's15-local-resolvers.envshexport the pod's resolver(s) intoNGINX_LOCAL_RESOLVERS, andNGINX_ENVSUBST_FILTERscopes the substitution to justBACKEND_HOSTandNGINX_LOCAL_RESOLVERS(so any$host/$remote_addrin the template stays as nginx vars, never accidentally substituted)..env.example: drop the now-runtimeVITE_KEYCLOAK_*/VITE_AUTH_ENABLED; document where to edit them for local dev (public/config.json).0.2.0in bothapps/smartem/package.jsonandapps/smartem/src/version.ts. The release pipeline will tagsmartem-frontend-v0.2.0after merge.Skipped on purpose:
packages/api/src/generated/**. The release workflow regenerates these at build time; the same OpenAPI spec yielded only orval-version-comment + timestamp churn. Out of scope.version-check.tswiring) is independent; not bundled here.Local verification
npm ci,npm run typecheck,npm run check,npm run build:smartemall cleandocker build .succeeds end-to-endBACKEND_HOSTand serves/,/version,/config.json, SPA history fallbackdocker run -v /tmp/x.json:/usr/share/nginx/html/config.jsonproves the ConfigMap-mount pattern overrides the placeholderGET /api/reverse-proxies through and returns the backend's response (verified with a localnginx:alpinemock)default.conf(inside the running container) has the correctresolverline andset $backend_upstream "smartem-http-api-service"directiveFollow-ups (Phase B, smartem-devtools)
Once this lands and
0.2.0is on GHCR, the k8s manifests + ingress for the frontend can land in smartem-devtools (deployment+service per env, ConfigMap with per-env keycloak/auth values, kustomization updates). That's the next PR in this series.Test plan
ghcr.io/diamondlightsource/smartem-frontend:0.2.0rc{n}and:latestis unchanged (RC, not stable)smartem-frontend-v0.2.0to produce the stable release once Phase B is ready to consume it