# Прод-compose food-market. # # ВАЖНО (2026-10-05): у food-market свой postgres прода — контейнер # `food-market-prod-postgres`, том `food-market-prod-postgres-data`, порт # `127.0.0.1:5437`. На prod-vm параллельно существует отдельный compose-проект # `shared-postgres` (~/shared-postgres/docker-compose.yml, порт 5434, том # `food-market-postgres-data` *external*) с ЧУЖИМИ боевыми базами (sklad, # purchase, calcman, calcman_staging, makesales). Он food-market НЕ принадлежит # и в этот compose возвращаться НЕ должен. См. docs/shared-postgres-followup-progress.md. # # Guards в ~/deploy-stage.sh и ~/quality-watchdog.sh падают, если здесь появится # `shared-postgres`, `food-market-postgres-data`, порт `5434` или старое имя # контейнера `food-market-postgres`. services: postgres: image: ${REGISTRY:-127.0.0.1:5001}/mirror/postgres:16-alpine container_name: food-market-prod-postgres restart: unless-stopped environment: POSTGRES_DB: food_market POSTGRES_USER: food_market POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-food_market_dev} PGDATA: /var/lib/postgresql/data/pgdata # prod-vm занят: 5432 — host postgres, 5434 — shared-postgres (чужой), # 5435 — stage-postgres, 5436 — food-market-server-postgres (сосед). # Нам остаётся 5437. ports: - "127.0.0.1:5437:5432" volumes: - postgres-data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U food_market -d food_market"] interval: 10s timeout: 5s retries: 5 api: image: ${REGISTRY:-127.0.0.1:5001}/food-market-api:${API_TAG:-latest} container_name: food-market-api restart: unless-stopped depends_on: postgres: condition: service_healthy environment: ASPNETCORE_ENVIRONMENT: Production ConnectionStrings__Default: Host=postgres;Port=5432;Database=food_market;Username=food_market;Password=${POSTGRES_PASSWORD:-food_market_dev} # Публичный issuer токенов — обязателен за прокси, иначе берётся из запроса # (или дефолт localhost из appsettings, что неверно для прод). OpenIddict__Issuer: ${OPENIDDICT_ISSUER:-https://admin.food-market.kz/} # Пароль PFX-сертификатов OpenIddict (пусто = сертификаты без пароля). OpenIddict__CertPassword: ${OPENIDDICT_CERT_PASSWORD:-} # Sprint 13: rate-limit на signup. На stage'е переопределяется в # .env'е через RATE_SIGNUP_HOUR / RATE_SIGNUP_DAY для прохождения # e2e/smoke; в prod'е оставляем дефолты 3/час, 10/сутки. RateLimiting__SignupPerIpPerHour: ${RATE_SIGNUP_HOUR:-3} RateLimiting__SignupPerIpPerDay: ${RATE_SIGNUP_DAY:-10} # Sprint 30: «быстрый вход» на тестовом стенде. По умолчанию выключено. # На stage включается через QUICK_LOGIN_ENABLED=true в .env. # QUICK_LOGIN_MASTER_PASSWORD задаётся секрет-паролем стенда (значение # хранится вне репозитория — на dev-vm в ~/.fm-watchdog/stage-shared- # password). Если переменная пустая — master-password-режим отключён # (IsMasterPassword отбивает пустую строку). SPA отправляет публичный # sentinel '__QUICK_LOGIN_PWLESS__' — реальный пароль в JS не попадает. # Code имеет safety-override: даже с Enabled=true, если OpenIddict__ # Issuer смотрит на admin.food-market.kz — быстрый вход принудительно # выключен (см. QuickLoginOptions.EffectivelyEnabled). QuickLogin__Enabled: ${QUICK_LOGIN_ENABLED:-false} QuickLogin__MasterPassword: ${QUICK_LOGIN_MASTER_PASSWORD:-} # Прод: начальный SuperAdmin (DevDataSeeder) и отключение демо-данных для # чистого (пустого) старта. На проде SEED_DEMO_DATA=false → без «Demo Market». # Пустой SEED_SUPERADMIN_PASSWORD = SuperAdmin не сидируется (безопасный дефолт). SEED_SUPERADMIN_PASSWORD: ${SEED_SUPERADMIN_PASSWORD:-} SEED_DEMO_DATA: ${SEED_DEMO_DATA:-} # Host port mapping: pick free ports on existing stage server (80/443 taken by # legacy nginx, 5000/5002/5005 taken by legacy .NET apps). ports: - "8080:8080" # api healthcheck: # Готовность = БД отвечает + миграции применены (см. /health/ready). test: ["CMD-SHELL", "curl -fsS http://localhost:8080/health/ready || exit 1"] interval: 30s timeout: 5s retries: 5 start_period: 30s volumes: - api-data:/app/App_Data - api-logs:/app/logs - /opt/food-market-data/uploads:/app/uploads web: image: ${REGISTRY:-127.0.0.1:5001}/food-market-web:${WEB_TAG:-latest} container_name: food-market-web restart: unless-stopped depends_on: api: condition: service_healthy ports: - "8081:80" # web SPA, not on 80 (legacy nginx holds it) public: image: ${REGISTRY:-127.0.0.1:5001}/food-market-public:${PUBLIC_TAG:-latest} container_name: food-market-public restart: unless-stopped ports: - "8082:80" # marketing astro static volumes: postgres-data: # ВАЖНО: НЕ `food-market-postgres-data` — тот том принадлежит отдельному # проекту shared-postgres (чужие боевые базы). См. docs/shared-postgres-followup-progress.md. name: food-market-prod-postgres-data api-data: name: food-market-api-data api-logs: name: food-market-api-logs