# Production compose. Pulls pre-built images from GHCR # (built and pushed by .github/workflows/deploy.yml). # # Place at /opt/gendesign/docker-compose.prod.yml on the VM. # Required env in /opt/gendesign/.env: # POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD # IMAGE_TAG (defaults to "latest") # # Optional (LLM chat #960/#957 — dormant until BOTH set; written to # backend/.env.runtime by deploy.yml from Forgejo secret/var only when non-empty): # OPENAI_API_KEY — Forgejo Actions secret (sensitive); unset → openai_api_key=None # LLM_ENABLED — Forgejo Actions variable (bool, "true"); unset → llm_enabled=False # # In production the browser talks to Caddy on :80/:443. # Caddy proxies /api/* and /health straight to backend, the rest to frontend. # So the frontend uses same-origin relative URLs — no NEXT_PUBLIC_API_BASE_URL needed. # # Postgres + Redis run alongside the app on the same VM (Discovery mode). # Volumes are shared with docker-compose.yml so switching between files preserves data. services: postgres: image: postgis/postgis:16-3.4 restart: unless-stopped environment: POSTGRES_DB: ${POSTGRES_DB} POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} # Bind to 127.0.0.1 only — accessible from host (for SSH tunnel) but not from public internet. # UFW additionally blocks 5432 from outside. ports: - "127.0.0.1:5432:5432" volumes: - postgres_data:/var/lib/postgresql/data - ./backend/db/init:/docker-entrypoint-initdb.d:ro healthcheck: test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER}"] interval: 5s timeout: 5s retries: 10 networks: default: {} shared: aliases: - gendesign-postgres redis: image: redis:7-alpine restart: unless-stopped volumes: - redis_data:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 3s retries: 5 # OSRM routing engine (#39 — site-finder /analyze road/walking distances to POI # вместо straight-line ST_Distance). INFRA-only здесь: интеграция в /analyze — # отдельный follow-up (A2/A3). Backend ходит к нему по http://osrm:5000 через # default-сеть. НЕ публикуется наружу (нет ports:) — internal-only, Caddy его # не проксирует. # # Region: Свердловская обл (oblast-clip из Ural-FO extract). Префикс .osrm-файлов # задаётся OSRM_REGION (default `sverdlovsk`). Файлы /data/.osrm* кладёт # scripts/build_osrm.sh в ./data/osrm/ (host) → /data (container). Артефакты # .gitignored (не коммитим ~1.5GB build output) — на VPS строятся offline. # # --algorithm mld → pipeline extract→partition→customize (НЕ contract). max-table-size # поднят для будущих many-to-many table-запросов (изохроны A2). # # ВАЖНО: пока scripts/build_osrm.sh не отработал, /data/.osrm нет → # osrm-routed падает и контейнер crash-loop'ит (restart: unless-stopped). Это # БЕЗВРЕДНО для деплоя: backend НЕ depends_on osrm, поэтому `compose up -d` в # deploy.yml не блокируется. Оркестратор строит граф offline и поднимает osrm # отдельно (docs/osrm-routing.md). osrm: image: osrm/osrm-backend:latest restart: unless-stopped command: osrm-routed --algorithm mld --max-table-size 8000 /data/${OSRM_REGION:-sverdlovsk}.osrm volumes: - ./data/osrm:/data mem_limit: 1.5g # TCP-probe порта 5000 через bash /dev/tcp. osrm/osrm-backend (Debian-slim) # НЕ содержит curl/wget — HTTP-healthcheck падал бы на missing binary (ср. # glitchtip #644). bash в образе есть → /dev/tcp надёжно проверяет, что # osrm-routed слушает (= граф загружен в RAM, движок принимает запросы). # start_period щедрый: загрузка MLD-графа Свердл в RAM занимает время. # Полный route-probe (driving) — в scripts/build_osrm.sh и в PR-инструкции # для оркестратора (curl с хоста VPS, см. docs/osrm-routing.md). healthcheck: test: ["CMD-SHELL", "timeout 3 bash -c '