## The bug
Self-hosted production image published on a non-default port (`docker
run -p 8080:80`, the default `APP_PORT` in
`docker-compose.production.yml`) serves the page but with **no CSS and
no JS**: every Vite asset is requested from
`http://<host>/build/assets/...` — port 80, where nothing listens —
while the page itself is on `:8080`.
## Root cause
Debian's nginx package now ships `/etc/nginx/fastcgi_params` with a
security workaround:
```nginx
# !!! Security workaround !!!
# Do not use HTTP_HOST as "$http_host".
...
# Note: this changes behaviour compared to previous versions, because "$host"
# does not preserve the client-supplied port [...] Existing deployments that
# rely on "$http_host" containing a port number may therefore break.
fastcgi_param HTTP_HOST $host;
```
`docker/nginx/nginx.conf` does `include fastcgi_params;`, so PHP
receives `HTTP_HOST=192.168.20.46` instead of `192.168.20.46:8080`.
`Request::getPort()` then falls back to the scheme default,
`$request->root()` loses the port, and every absolute URL Laravel builds
— Vite assets, the `Link: rel=preload` header, redirects, mail links,
OAuth redirect URIs — points at port 80.
Deployments behind a reverse proxy on 80/443 don't see it: the proxy
sends `X-Forwarded-Port`, which takes precedence.
## The fix
Ship our own `docker/nginx/fastcgi_params` with `HTTP_HOST $http_host`,
so the value no longer depends on what the base image happens to
install.
## Verification
Reproduced in a minimal container (`php:8.4-fpm` + apt nginx + this
repo's `nginx.conf`, repo bind-mounted at `/app`), printing
`$_SERVER['HTTP_HOST']` and `app('url')->asset('build/assets/app.css')`:
| fastcgi_params | `HTTP_HOST` | `asset()` |
| --- | --- | --- |
| from the base image | `127.0.0.1` |
`http://127.0.0.1/build/assets/app.css` ❌ |
| this PR | `127.0.0.1:8096` |
`http://127.0.0.1:8096/build/assets/app.css` ✅ |
`nginx -t` passes, and a request with a domain `Host` (no port) still
generates `http://whispermoney.example/build/...` unchanged.
Guarded by `tests/Unit/ProductionNginxConfigTest.php`.
## Note
Restoring `$http_host` re-exposes the case Debian's workaround targets
(a raw client `Host` that differs from an absolute-form request target).
The image already forwards the client `Host` — `server_name _` accepts
anything and there is no `TrustHosts` middleware — so this doesn't widen
the surface, but closing it properly would mean an explicit
`server_name` plus a `default_server` that rejects unknown hosts.
---------
Co-authored-by: Víctor Falcón <victor.falcon@factorial.co>