fix(docker): enable uvicorn proxy-headers so CSS loads behind Traefik #35

Merged
archeious merged 1 commit from fix/proxy-headers into main 2026-04-19 18:16:16 -06:00
Collaborator

Summary

The first production deploy rendered unstyled — CSS and brand images
missing. Root cause: docker/entrypoint.sh launches uvicorn without
--proxy-headers, so Starlette ignores X-Forwarded-Proto from
Traefik and falls back to the internal scheme/host when building
URLs via url_for(). Templates therefore emit e.g.
<link rel="stylesheet" href="http://<internal>/static/app.css">,
which the browser blocks as mixed content on the https:// page.

Fix: add --proxy-headers --forwarded-allow-ips='*' to the uvicorn
invocation in the entrypoint. Safe to trust all forwarded IPs because
compose.yml publishes no host port — only containers on proxy-net
(Traefik) can reach port 8000.

Reproduction

Against the pre-fix image:

$ curl -sS -H 'Host: quartermaster.unbiasedgeek.com' \
       -H 'X-Forwarded-Proto: https' \
       http://127.0.0.1:18001/ | grep stylesheet
  <link rel="stylesheet" href="http://127.0.0.1:18001/static/app.css">

Against the rebuilt image with the fix:

$ curl -sS -H 'Host: quartermaster.unbiasedgeek.com' \
       -H 'X-Forwarded-Proto: https' \
       http://127.0.0.1:18001/ | grep stylesheet
  <link rel="stylesheet" href="https://quartermaster.unbiasedgeek.com/static/app.css">

Test plan

  • Reproduced the bad URL generation with the current prod image.
  • Confirmed the fix renders https://quartermaster.unbiasedgeek.com/static/…
    when Traefik-style headers are present.
  • /healthz still returns 200 and JSON logs still stream.
  • End-to-end: merge rolls the new image, browser reloads with CSS.
## Summary The first production deploy rendered unstyled — CSS and brand images missing. Root cause: `docker/entrypoint.sh` launches uvicorn without `--proxy-headers`, so Starlette ignores `X-Forwarded-Proto` from Traefik and falls back to the internal scheme/host when building URLs via `url_for()`. Templates therefore emit e.g. `<link rel="stylesheet" href="http://<internal>/static/app.css">`, which the browser blocks as mixed content on the `https://` page. Fix: add `--proxy-headers --forwarded-allow-ips='*'` to the uvicorn invocation in the entrypoint. Safe to trust all forwarded IPs because `compose.yml` publishes no host port — only containers on `proxy-net` (Traefik) can reach port 8000. ## Reproduction Against the pre-fix image: ``` $ curl -sS -H 'Host: quartermaster.unbiasedgeek.com' \ -H 'X-Forwarded-Proto: https' \ http://127.0.0.1:18001/ | grep stylesheet <link rel="stylesheet" href="http://127.0.0.1:18001/static/app.css"> ``` Against the rebuilt image with the fix: ``` $ curl -sS -H 'Host: quartermaster.unbiasedgeek.com' \ -H 'X-Forwarded-Proto: https' \ http://127.0.0.1:18001/ | grep stylesheet <link rel="stylesheet" href="https://quartermaster.unbiasedgeek.com/static/app.css"> ``` ## Test plan - [x] Reproduced the bad URL generation with the current prod image. - [x] Confirmed the fix renders `https://quartermaster.unbiasedgeek.com/static/…` when Traefik-style headers are present. - [x] `/healthz` still returns 200 and JSON logs still stream. - [ ] End-to-end: merge rolls the new image, browser reloads with CSS.
claude-code added 1 commit 2026-04-19 18:15:44 -06:00
Without --proxy-headers + --forwarded-allow-ips, uvicorn ignores the
X-Forwarded-Proto header Traefik sets, so Starlette's url_for() picks
up the internal scheme (http) and host (the container's bind address).
That makes every <link>/<img> href in templates point at an internal
URL with the wrong scheme — the browser refuses CSS/images as mixed
content and the public page renders unstyled.

With both flags the template output becomes
https://quartermaster.unbiasedgeek.com/static/… as expected.

The wildcard in --forwarded-allow-ips='*' is safe here because the
compose file publishes no host ports — only containers on proxy-net
(i.e. Traefik) can reach port 8000.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
archeious approved these changes 2026-04-19 18:16:13 -06:00
archeious merged commit 5ae0675705 into main 2026-04-19 18:16:16 -06:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: archeious/quartermaster#35
No description provided.