From 6831f650d90a0a41d17749e0da20fd1c0e3a3f8f Mon Sep 17 00:00:00 2001 From: Roberto Date: Mon, 10 Aug 2026 10:09:44 -0300 Subject: [PATCH 1/2] fix: keep Traefik router up under Docker health + host mode Exempt /health from rate limits, disable the image HEALTHCHECK in the gVisor compose (Traefik v3 drops unhealthy routers), and point the service at 127.0.0.1:8000 for host-network Traefik. Co-authored-by: Cursor --- deploy/docker-compose.gvisor.yml | 19 +++++++------------ docs/DEPLOY_GVISOR.md | 17 +++++++++++++++++ src/findata/api/app.py | 3 ++- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/deploy/docker-compose.gvisor.yml b/deploy/docker-compose.gvisor.yml index 56a4ee5..af755c4 100644 --- a/deploy/docker-compose.gvisor.yml +++ b/deploy/docker-compose.gvisor.yml @@ -31,21 +31,16 @@ services: cpus: 1.0 user: "65534:65534" pids_limit: 256 + # Traefik v3 drops routers for Docker-unhealthy containers. The image + # HEALTHCHECK hits /health; disable it here so a rate-limit blip cannot + # take the public router offline. /health remains exempt in the app. + healthcheck: + disable: true environment: FINDATA_RATE_LIMIT_ENABLED: "true" FINDATA_RATE_LIMIT_DEFAULT: "60/minute;1000/day" # Explicitly off — do not override via .env on public VPS. FINDATA_MCP_CODE_MODE: "0" - healthcheck: - test: - - "CMD" - - "python" - - "-c" - - "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://localhost:8000/health', timeout=2).status==200 else 1)" - interval: 30s - timeout: 3s - retries: 3 - start_period: 5s logging: driver: json-file options: @@ -55,12 +50,12 @@ services: - openfindata_net labels: - traefik.enable=true - - traefik.docker.network=deploy_openfindata_net + # Traefik runs in host network mode on this VPS — reach the published loopback port. # Production: export OPENFINDATA_HOST=api.seudominio.com before up. - traefik.http.routers.openfindata.rule=Host(`${OPENFINDATA_HOST:-findata.localhost}`) - traefik.http.routers.openfindata.entrypoints=websecure - traefik.http.routers.openfindata.tls.certresolver=letsencrypt - - traefik.http.services.openfindata.loadbalancer.server.port=8000 + - traefik.http.services.openfindata.loadbalancer.server.url=http://127.0.0.1:8000 networks: openfindata_net: diff --git a/docs/DEPLOY_GVISOR.md b/docs/DEPLOY_GVISOR.md index 2090923..6e71190 100644 --- a/docs/DEPLOY_GVISOR.md +++ b/docs/DEPLOY_GVISOR.md @@ -65,6 +65,23 @@ O compose publica só em `127.0.0.1:8000` e usa labels Traefik. **Não** anexe e rede a stacks hermes/wealthuman. **Não** habilite code mode (`FINDATA_MCP_CODE_MODE` deve permanecer ausente). + +## Traefik host mode + health + +Traefik on this VPS uses `network_mode: host`. Point the service at the published +loopback port: + +```yaml +traefik.http.services.openfindata.loadbalancer.server.url=http://127.0.0.1:8000 +``` + +Do **not** set `traefik.docker.network=...` for this layout. + +Traefik v3 **drops routers for Docker-unhealthy containers**. If `/health` is rate +limited, the container goes unhealthy and public HTTPS returns Traefik +`404 page not found` even while `curl 127.0.0.1:8000/health` still works. +`/health` is rate-limit exempt for that reason. + ## Smoke checks ```bash diff --git a/src/findata/api/app.py b/src/findata/api/app.py index 700cbdf..8600cbb 100644 --- a/src/findata/api/app.py +++ b/src/findata/api/app.py @@ -225,8 +225,9 @@ async def meta() -> dict[str, object]: @app.get("/health", tags=["Meta"]) +@limiter.exempt async def health() -> dict[str, str]: - """Liveness probe.""" + """Liveness probe (exempt from rate limits so Docker/Traefik healthchecks stay green).""" return {"status": "ok", "version": _VERSION} From 456997c755f5d2d1b9a50f73604656527af7df14 Mon Sep 17 00:00:00 2001 From: Roberto Date: Mon, 10 Aug 2026 10:10:26 -0300 Subject: [PATCH 2/2] fix: silence mypy on SlowAPI exempt decorator for /health Co-authored-by: Cursor --- src/findata/api/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/findata/api/app.py b/src/findata/api/app.py index 8600cbb..84c71ee 100644 --- a/src/findata/api/app.py +++ b/src/findata/api/app.py @@ -225,7 +225,7 @@ async def meta() -> dict[str, object]: @app.get("/health", tags=["Meta"]) -@limiter.exempt +@limiter.exempt # type: ignore[untyped-decorator] async def health() -> dict[str, str]: """Liveness probe (exempt from rate limits so Docker/Traefik healthchecks stay green).""" return {"status": "ok", "version": _VERSION}