The first version of vhost.d/<host>_location nested four `location { … }`
blocks (for /_astro/, images, /sw.js, /llms.txt) inside the proxy's
generated `location / { … }` to set Cache-Control. nginx accepts the
syntax, but a nested location with no `proxy_pass` directive falls through
to filesystem root and 404s the asset — which is why CSS / JS / images
were missing on the live site even though the HTML loaded fine.
Astro already emits sensible Cache-Control on hashed _astro bundles, so
we don't need the proxy to set them. Removed all four nested blocks; the
vhost.d files now only carry proxy headers, gzip, and security headers,
all of which are valid inside a location {} block without proxy_pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The acme-companion on the production host doesn't accept comma-separated
VIRTUAL_HOST / LETSENCRYPT_HOST values, so cert issuance was failing for
the combined `nibiru-framework.com,www.nibiru-framework.com` entry.
docker-compose.yml — now defines two services sharing the same image:
- docs → VIRTUAL_HOST=nibiru-framework.com (apex)
- docs-www → VIRTUAL_HOST=www.nibiru-framework.com (built once, reused)
A YAML anchor (x-docs-shared-env) keeps the Oracle/LLM/Anthropic config in
lockstep so the two containers can never drift.
docs/nginx/vhost.d/ — per-host nginx-proxy overrides applied at the
location-block level by jwilder/nginx-proxy. Both files set:
- X-Forwarded-* trust + buffering off (Oracle SSE streaming)
- HSTS / X-Content-Type / X-Frame / Referrer-Policy / Permissions-Policy
- gzip with the right MIME set for Astro/Starlight assets
- Aggressive cache on /_astro/ (immutable hashed bundles)
- 30-day cache on images/fonts
- no-store on /sw.js (so PWA updates land)
- 24-hour cache on /llms.txt for AI crawlers
docs/nginx/README.md explains how to mount these into an existing
nginx-proxy (bind-mount + reload, or bake into the proxy image).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>