docs(docker-dev): first-POST-404 caught — Traefik entrypoint-before-dynamic-config window + no-body POST binding verified
Live gate on the local OrbStack docker-dev rig (branch tip images, fresh down -v + up -d --build). 404: did NOT reproduce through deploy.sh on a fresh boot — the readiness gate passed on its first poll, and compose's depends_on: service_healthy serialization means up -d returns ~30 s after Traefik starts, so the window is already closed. Reproduced it directly instead, by rapid-polling :9200 across a traefik container restart: connection-refused -> 404 for ~65-160 ms -> 200. Body is exactly Go's "404 page not found" (19 bytes) and the access log records it with an EMPTY RouterName/ServiceName at 0ms, so no router matched and no backend was dialled. Mechanism: the entrypoint binds before the dynamic file-provider config is loaded. The competing candidate (health- checked service with no healthy server) is excluded — that is a matched router answering 503, and there are zero 503s in the log. Caveat recorded: the earliest 404s predate the accesslog handler itself and are not logged, so an empty access log does not prove absence. No-body POST: verified, not 400. The minimal-API DeployRequest? binds both a missing body and a Content-Length: 0 body as null; the handler falls back to createdBy = "rest-api". A bare ./docker-dev/deploy.sh answered 202 Accepted and the Deployment row carried CreatedBy = rest-api. deploy.sh's header NOTE goes from "unverified" to the observed truth.
This commit is contained in:
+20
-7
@@ -8,23 +8,36 @@
|
||||
# end of the request: the AdminUI maps every route synchronously before RunAsync and
|
||||
# the deploy endpoint has no 404 path (503/401/422/409/500 only), and Traefik answers
|
||||
# a matched-router-with-all-servers-down as 503 "no available server". So the 404 means
|
||||
# no Traefik router matched at that instant. Polling /health/ready through :9200 until
|
||||
# it answers 200 proves both halves are in place — the router table carries the
|
||||
# otopcua-admin router AND a backend is up — before the single, un-retried POST.
|
||||
# no Traefik router matched at that instant.
|
||||
#
|
||||
# CONFIRMED 2026-08-18: the window is between Traefik binding the `web` entrypoint and
|
||||
# loading its dynamic file-provider config — the port accepts connections before the
|
||||
# router table exists, so requests get Go's default `404 page not found` (19 bytes),
|
||||
# access-logged with an EMPTY RouterName/ServiceName and 0ms. Measured at ~65-160 ms
|
||||
# across a `docker restart` of the traefik container. See docker-dev/README.md
|
||||
# §"First-POST 404". Polling /health/ready through :9200 until it answers 200 proves
|
||||
# both halves are in place — the router table carries the otopcua-admin router AND a
|
||||
# backend is up — before the single, un-retried POST.
|
||||
#
|
||||
# A 404 AFTER the gate is therefore new evidence, not the known boot race: it should be
|
||||
# investigated against the Traefik access log (`--accesslog=true` on the traefik service;
|
||||
# `docker compose -f docker-dev/docker-compose.yml logs traefik`), which records the
|
||||
# RouterName (empty on an unmatched request) for the failing call.
|
||||
# RouterName (empty on an unmatched request) for the failing call. Caveat: the earliest
|
||||
# 404s of the window predate the accesslog handler itself and are NOT logged at all, so
|
||||
# an empty access log does not prove the 404 did not happen.
|
||||
#
|
||||
# Usage:
|
||||
# ./deploy.sh # no body (matches the bare README snippet)
|
||||
# ./deploy.sh artifact.json # send that file as the JSON request body
|
||||
# cat artifact.json | ./deploy.sh
|
||||
#
|
||||
# NOTE (unverified against a live rig): the no-body invocation sends
|
||||
# `Content-Type: application/json` with an empty body. Whether the minimal-API
|
||||
# `DeployRequest?` parameter binds that as null or answers 400 has not been confirmed.
|
||||
# NOTE (verified on the docker-dev rig 2026-08-18): the no-body invocation sends
|
||||
# `Content-Type: application/json` with no body (or `Content-Length: 0` when stdin is a
|
||||
# redirected-but-empty non-tty, which takes the `--data-binary @-` branch below). The
|
||||
# minimal-API `DeployRequest?` parameter binds BOTH shapes as null — it does NOT 400 —
|
||||
# and the handler falls back to `createdBy = "rest-api"` (DeployApiEndpoints.cs). Confirmed
|
||||
# end to end: a bare `./deploy.sh` returned `HTTP 202 {"outcome":"Accepted",...}` and the
|
||||
# resulting Deployment row carried `CreatedBy = rest-api`.
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
|
||||
Reference in New Issue
Block a user