Repository navigation
Runtime-configurable frontend API URL + env-driven CORS whitelist - #17
Merged
Merged
Conversation
Fixes AVI-42. Live blocker from a real Portainer deployment on a LAN IP (http://192.168.1.10:8180). Confirmed via HAR: the frontend called http://localhost:8000/api/... (response.status: 0 — never reached Django), because VITE_API_BASE_URL is baked into the JS bundle at build time. One published image can only ever point at one backend host. - frontend/runtime-config-entrypoint.sh: new entrypoint that generates runtime-config.js from $API_BASE_URL on every container *start*, not build. Chains into nginx's own official entrypoint (renamed to avoid colliding with its conventional /docker-entrypoint.sh path) rather than replacing it. - constants.js: precedence is now runtime env (window.__RUNTIME_CONFIG__) > build-time Vite env > hardcoded fallback. - nginx.conf: exact-match location for runtime-config.js with Cache-Control: no-cache, so a restarted container with a different $API_BASE_URL isn't served stale. - index.html: loads runtime-config.js before main.js; onerror fallback for local `pnpm dev` where the file doesn't exist. Second half — even with the URL fixed, CORS_ALLOWED_ORIGINS/ CSRF_TRUSTED_ORIGINS in development.py was a hardcoded Python list (localhost:8000/8080/5173 only), which would reject the request next: - development.py: both now read from comma-separated env vars via a new _env_list() helper, defaulting to the exact same hardcoded values as before — no behavior change for existing local dev. - docker-compose.yml: both new env vars added with defaults matching current behavior, documented inline with what to change for a non-localhost deployment. ## Test plan - [x] Reproduced the exact reported failure: OPTIONS preflight with Origin: http://192.168.1.10:8180 against default config returns no access-control-* headers - [x] Confirmed the fix: same request, with CORS_ALLOWED_ORIGINS overridden via a real container env var (matching how Portainer sets env vars, not shell-exported vars before `docker compose up`, which don't override literal compose values) — now returns the correct access-control-allow-origin header - [x] Full round trip: an actual POST /api/auth/register/ with that Origin now returns 201 Created - [x] Confirmed default/unset behavior is unchanged - [x] Rebuilt and pushed both crow987/avitrail:latest and crow987/avitrail-frontend:latest with the fix — live on Docker Hub, verified via docker manifest inspect - [x] 66 backend tests, 100% coverage; eslint clean
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.
Fixes AVI-42. Live blocker.
Summary
Deploying to Portainer on a LAN IP (
http://192.168.1.10:8180) failed. Confirmed via HAR from the actual browser session: the frontend calledhttp://localhost:8000/api/...(response.status: 0— the request never reached Django at all), becauseVITE_API_BASE_URLis baked into the JS bundle at build time. A single publishedcrow987/avitrail-frontend:latestimage can only ever point at one backend host, which doesn't work for more than one deployment.Even with that fixed, the next wall: Django's
CORS_ALLOWED_ORIGINS/CSRF_TRUSTED_ORIGINSindevelopment.pywas a hardcoded Python list (localhost:8000/8080/5173only) —http://192.168.1.10:8180would be rejected outright.Frontend fix:
frontend/runtime-config-entrypoint.sh: new container entrypoint that generatesruntime-config.jsfrom$API_BASE_URLon every container start (not build) — chains into nginx's own official entrypoint rather than replacing it.constants.js: precedence is now runtime env (window.__RUNTIME_CONFIG__) > build-time Vite env > hardcoded fallback.nginx.conf: exact-match location forruntime-config.jswithCache-Control: no-cache, so a restarted container with a different$API_BASE_URLisn't served stale.index.html: loadsruntime-config.jsbeforemain.js;onerrorfallback for localpnpm devwhere the file doesn't exist.Backend fix:
development.py:CORS_ALLOWED_ORIGINS/CSRF_TRUSTED_ORIGINS/ALLOWED_HOSTSnow read from comma-separated env vars via a new_env_list()helper, defaulting to the exact same hardcoded values as before — no behavior change for existing local dev.docker-compose.yml: both new env vars added with defaults matching current behavior, documented inline with what to change for a non-localhost deployment.Test plan
Origin: http://192.168.1.10:8180against default config returns noaccess-control-*headersCORS_ALLOWED_ORIGINSoverridden via a real container env var (matching how Portainer sets env vars, not shell-exported vars beforedocker compose up, which don't override literal compose values) — now returns the correctaccess-control-allow-originheaderPOST /api/auth/register/with that Origin now returns201 Createdcrow987/avitrail:latestandcrow987/avitrail-frontend:latestwith the fix — live on Docker Hub now, verified viadocker manifest inspect🤖 Generated with Claude Code