Forward-fix после incident May 14 (см. vault events/Incident_Prod_Down_May14_2026).
PR #117 был полностью reverted PR #119 (Caddy depends_on frontend: service_healthy
+ failing wget --spider / → Caddy не стартовал → vsё прод down ~25 мин).
Этот PR делает то же что планировал #117, но корректно:
### Frontend healthcheck — TCP probe через node (НЕ HTTP)
```yaml
test: ["CMD", "node", "-e",
"require('net').createConnection({port:3000,host:'127.0.0.1'})
.once('connect',function(){process.exit(0)})
.once('error',function(){process.exit(1)})"]
start_period: 60s
retries: 6
interval: 15s
```
- НЕ зависит от HTTP status (200/404/308 — все OK если порт слушает)
- `node` гарантированно в alpine image (запускает server.js)
- `127.0.0.1` — никакой DNS ambiguity (busybox alpine localhost IPv6 vs IPv4)
- 60s + 6×15s = 150s window — с запасом на Next.js standalone cold start
### Caddy depends_on — backend healthy, frontend started (НЕ healthy)
```yaml
caddy:
depends_on:
backend: { condition: service_healthy } # backend имеет /health endpoint
frontend: { condition: service_started } # safety net — НЕ блокирует Caddy
```
Lesson from incident: frontend health flaky (Next.js cold start variable).
Strict service_healthy для frontend → одна misconfig = full outage обоих доменов
(gendsgn.ru + obsidian.gendsgn.ru через тот же Caddy на shared network).
### backend/worker/beat redis: service_started → service_healthy
Redis уже имеет `redis-cli ping` healthcheck. Tightening безопасно — был
artifact pre-healthcheck Redis.
### Pin rosreestr2coord>=5.0.0 (lock уже 5.3.3)
v5 убрала delay параметр; код адаптирован (rate limit на worker уровне).
Защита от silent downgrade при `uv lock --upgrade`.
### Что НЕ повторено из #117
- Caddy depends_on frontend: service_healthy — заменено на service_started
- wget --spider / — заменено на node TCP probe
### Vault обновлён
- `events/Incident_Prod_Down_May14_2026.md` — постмортем + timeline + lessons
- Update Changelog в `domains/infra/infra-MOC.md` (если есть)
Closes incident. Re-applies #117 goals safely.
Co-authored-by: lekss361 <claudestars@proton.me>
Two fixes for the NSPD geo bulk-fetcher:
1. rosreestr2coord was listed in pyproject.toml but missing from uv.lock,
so Docker's `uv sync --frozen` didn't install it. NSPD jobs failed every
target with `No module named 'rosreestr2coord'`. Ran `uv lock` to add it
(v5.3.3) plus requests + charset-normalizer transitive deps.
2. /admin/scrape/geo and /admin/scrape/objective lacked the log panel that
/admin/scrape (DomRF) had. Extracted ScrapeLogsPanel component over the
unified /api/v1/admin/scrape/all/logs endpoint with scraper_type filter
and wired into both pages.
Tests: ruff ✓, tsc --noEmit ✓
- backend/alembic/* — alembic infra (env.py, script.py.mako). versions/ empty
for now; first migration goes in Stage 2a when models are finalized.
- backend/Dockerfile: bake alembic.ini + alembic/ into the image so
`docker compose exec backend alembic upgrade head` works in prod.
- backend/db/init/99_drop_unused_extensions.sql + bind-mount in both compose
files: drops postgis-image's TIGER/topology/fuzzystrmatch on fresh volumes.
- .pre-commit-config.yaml + pre-commit in dev deps: ruff/prettier on commit
to stop CI failures like UP035 from leaking out.
- ops/backup.sh, ops/restore.sh: pg_dump cron script with optional Selectel S3
upload. 7-day local retention. Restore guard: requires RESTORE_CONFIRM=yes.
- Makefile: new targets `make migration NAME=...`, `make pre-commit-install`.
- backend/.env.example: SENTRY_DSN comment with sentry.io reference.
- backend/Dockerfile: split builder/runner, runtime libs only, non-root app user, curl for healthcheck, --frozen via new uv.lock
- frontend/Dockerfile: npm ci instead of npm install (deterministic), USER node
- docker-compose.prod.yml: working backend healthcheck (curl now in image), redis healthcheck, drop dead NEXT_PUBLIC_API_BASE_URL env (Next.js bakes NEXT_PUBLIC_* at build time, runtime override is no-op)
- docker-compose.yml: same redis healthcheck, BACKEND_URL for next.config rewrites, drop uv from CMD (not in new image)
- Caddyfile: handle (not handle_path) so /api prefix is preserved into FastAPI router
- next.config.ts: rewrite /api/* and /health to BACKEND_URL in dev (no Caddy locally)
- frontend/src/lib/api.ts: empty default = same-origin relative URLs
- Makefile: drop uv run from migrate target
- Add backend/uv.lock and frontend/package-lock.json for reproducible builds
Verified: docker build succeeds for both, backend container starts, /health responds, curl healthcheck works inside image, container runs as non-root.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>