Skip to content

feat(deploy): configurable nginx upstreams via env vars (#183)#189

Merged
amavashev merged 3 commits into
mainfrom
feat/configurable-nginx-upstreams
May 31, 2026
Merged

feat(deploy): configurable nginx upstreams via env vars (#183)#189
amavashev merged 3 commits into
mainfrom
feat/configurable-nginx-upstreams

Conversation

@amavashev
Copy link
Copy Markdown
Contributor

Closes #183.

Problem

The dashboard image bundles an nginx reverse proxy that splits /v1/* traffic between the governance plane (/v1/*) and the runtime plane (/v1/reservations/*). The two upstream targets were hardcoded in nginx.conf, so retargeting them (split hosts, external endpoints, non-default ports) meant editing the config and rebuilding the image.

Change

Ship the proxy config as an envsubst template (nginx.confdefault.conf.template) with ${ADMIN_UPSTREAM} / ${RUNTIME_UPSTREAM} placeholders, rendered by the stock nginx entrypoint (20-envsubst-on-templates.sh) at container start. Defaults (http://cycles-admin:7979 / http://cycles-server:7878) are baked into the image and match the compose service names, so existing deployments are unaffected. Override either var at deploy time — no file edit, no image rebuild:

dashboard:
  environment:
    ADMIN_UPSTREAM: https://admin.internal:7979
    RUNTIME_UPSTREAM: https://runtime.internal:7878

Why not the VITE_* approach from the issue

VITE_ADMIN_URL / VITE_RUNTIME_URL driving direct cross-origin fetch was declined (see AUDIT.md): VITE_* bakes at build time (one image per env, defeating the portable-artifact model), requires relaxing CSP connect-src 'self', turns on mandatory CORS + preflight for X-Admin-API-Key on both backends, and sends the admin key cross-origin. Keeping the same-origin bundled proxy avoids all four.

Files

  • nginx.confdefault.conf.template — upstreams now placeholders
  • Dockerfile — copy template to /etc/nginx/templates/; bake ENV defaults
  • docker-compose.prod.yml — document the two vars on the dashboard service
  • CI: e2e.yml + pr-container-scan.yml path filters nginx.confdefault.conf.template
  • Docs: README, OPERATIONS, AUDIT (+ baseline cycles-server .15.17 fix), CHANGELOG, package.json 0.1.25.600.1.25.61

Validation

  • Ran the real nginx:1.29-alpine3.23 entrypoint with override upstreams; the entrypoint launched 20-envsubst-on-templates.sh and nginx -t reported syntax OK / test successful on the rendered config.
  • Replicated the entrypoint's envsubst step locally: both upstreams fill, all nginx runtime vars ($host / $request_uri / $upstream / …) are preserved, zero leftover ${…} placeholders.
  • No src/ change, so the Vitest suite is unaffected.

Deployment-infra only — no spec change, no admin-API surface delta, no client behaviour change.

The dashboard image bundles an nginx reverse proxy that splits /v1/*
traffic between the governance plane (/v1/*) and the runtime plane
(/v1/reservations/*). The two upstream targets were hardcoded, so
retargeting them (split hosts, external endpoints) meant editing the
config and rebuilding the image.

Ship the config as an envsubst template (nginx.conf ->
default.conf.template) with ${ADMIN_UPSTREAM} / ${RUNTIME_UPSTREAM}
placeholders, rendered by the stock nginx entrypoint at container start.
Bake defaults (cycles-admin:7979 / cycles-server:7878) matching the
compose service names so existing deployments are unaffected; override
either var at deploy time with no file edit and no image rebuild.

This keeps the same-origin proxy model, so no CSP/CORS/key-exposure
changes are needed (unlike the VITE_*-driven cross-origin approach
proposed in the issue, declined in AUDIT.md).

- Dockerfile: copy template to /etc/nginx/templates/; ENV defaults
- docker-compose.prod.yml: document the two vars on the dashboard service
- CI: e2e.yml + pr-container-scan.yml path filters nginx.conf -> template
- Docs: README, OPERATIONS, AUDIT (+ baseline cycles-server .15 -> .17
  fix), CHANGELOG, package.json 0.1.25.60 -> 0.1.25.61

Verified: real nginx:1.29-alpine3.23 entrypoint renders the template and
nginx -t passes; local envsubst replication confirms both upstreams fill
and nginx runtime vars ($host/$request_uri/$upstream) are preserved.
@amavashev amavashev merged commit ef53865 into main May 31, 2026
9 checks passed
@amavashev amavashev deleted the feat/configurable-nginx-upstreams branch May 31, 2026 17:22
amavashev added a commit that referenced this pull request May 31, 2026
PR #189 bumped package.json to 0.1.25.61 but left the lockfile's
top-level version at .60. Regenerated via `npm install --package-lock-only`
— only the two version fields change, no dependency tree churn. Keeps the
release-version alignment the release process requires before tagging.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Production deployment requires a reverse proxy to split /v1/ traffic

1 participant