From 154411e7a48d480ecf3d3f0f9234652215e4d146 Mon Sep 17 00:00:00 2001 From: danamir Date: Thu, 6 Aug 2026 14:44:52 +0300 Subject: [PATCH] Fix nginx crash-loop on a fully fresh docker compose up MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- docker-compose.yml | 5 +++++ src/Client/nginx.conf | 20 ++++++++++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 4bd1f44..4e5d547 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -37,6 +37,11 @@ services: build: context: . 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 # domain) is the actual public entry point and proxies here — see # docs/DEPLOY.md. For local `docker compose up`, still reachable at diff --git a/src/Client/nginx.conf b/src/Client/nginx.conf index 9a5e186..c0e47bc 100644 --- a/src/Client/nginx.conf +++ b/src/Client/nginx.conf @@ -2,11 +2,23 @@ server { listen 80; root /usr/share/nginx/html; - # No trailing slash on proxy_pass here — 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/. + # `server` is resolved as a variable (not a plain hostname in proxy_pass) + # so nginx uses Docker's embedded DNS resolver *lazily, per request* + # 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/ { - proxy_pass http://server:8080; + set $upstream_server http://server:8080; + proxy_pass $upstream_server; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;