Fix nginx crash-loop on a fully fresh docker compose up
Some checks failed
CI/CD / build-test-deploy (push) Has been cancelled
Some checks failed
CI/CD / build-test-deploy (push) Has been cancelled
nginx resolves a plain `proxy_pass http://server:8080;` hostname once at config-load time. On a cold `docker compose up` that creates every container at once, the `server` container isn't always registered in Docker's embedded DNS yet by the time the client's nginx starts — nginx then fails immediately ("host not found in upstream") and doesn't retry, so it just crash-loops. Route the target through a variable instead (`set $upstream_server ...; proxy_pass $upstream_server;`) with an explicit `resolver`, which makes nginx resolve the hostname lazily per-request via Docker's DNS rather than once at startup. Also add `depends_on: [server]` on the client service so it at least doesn't start before the server container exists at all. Verified with repeated `docker compose down && docker compose up` cycles locally — this only reliably reproduced starting every container from nothing simultaneously, which prior local testing hadn't actually done (the server container had usually stayed running across rebuilds).
This commit is contained in:
@@ -37,6 +37,11 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: src/Client/Dockerfile
|
dockerfile: src/Client/Dockerfile
|
||||||
|
# Doesn't fix the DNS race on its own (nginx.conf's lazy resolver does
|
||||||
|
# that) — just avoids nginx crashing into a restart loop for no reason
|
||||||
|
# by not starting client before `server` exists at all.
|
||||||
|
depends_on:
|
||||||
|
- server
|
||||||
# Loopback-only in production, where a host-level nginx (TLS + the real
|
# Loopback-only in production, where a host-level nginx (TLS + the real
|
||||||
# domain) is the actual public entry point and proxies here — see
|
# domain) is the actual public entry point and proxies here — see
|
||||||
# docs/DEPLOY.md. For local `docker compose up`, still reachable at
|
# docs/DEPLOY.md. For local `docker compose up`, still reachable at
|
||||||
|
|||||||
@@ -2,11 +2,23 @@ server {
|
|||||||
listen 80;
|
listen 80;
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
|
|
||||||
# No trailing slash on proxy_pass here — the full matched URI (including
|
# `server` is resolved as a variable (not a plain hostname in proxy_pass)
|
||||||
# the /api/ prefix) is forwarded unchanged, matching Giraffe's route
|
# so nginx uses Docker's embedded DNS resolver *lazily, per request*
|
||||||
# definitions in Server/Routes.fs, which all start with /api/.
|
# instead of resolving it once at config-load time. A static
|
||||||
|
# `proxy_pass http://server:8080;` fails permanently at nginx startup
|
||||||
|
# ("host not found in upstream") if the `server` container isn't already
|
||||||
|
# registered in Docker's DNS yet — a real race on a fully fresh
|
||||||
|
# `docker compose up` that starts every container at once, since nginx
|
||||||
|
# doesn't retry once it's already crashed.
|
||||||
|
resolver 127.0.0.11 valid=10s;
|
||||||
|
|
||||||
|
# No trailing slash on the proxy_pass target — the full matched URI
|
||||||
|
# (including the /api/ prefix) is forwarded unchanged, matching
|
||||||
|
# Giraffe's route definitions in Server/Routes.fs, which all start
|
||||||
|
# with /api/.
|
||||||
location /api/ {
|
location /api/ {
|
||||||
proxy_pass http://server:8080;
|
set $upstream_server http://server:8080;
|
||||||
|
proxy_pass $upstream_server;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
|||||||
Reference in New Issue
Block a user