whisper-money/docker
Jesús Mejías Leiva df9d62f76c
fix(docker): keep the client port in the URLs the app generates (#739)
## 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>
2026-08-09 14:25:19 +00:00
..
nginx fix(docker): keep the client port in the URLs the app generates (#739) 2026-08-09 14:25:19 +00:00
supervisor feat(docker): run the Laravel scheduler in the production image (#732) 2026-07-24 13:30:33 +02:00
entrypoint.sh fix(mcp): keep Passport signing keys at 600 in production (#693) 2026-07-17 21:20:00 +02:00