# ═══════════════════════════════════════════════════════════════════════════════ # ПРОД стек: локально — nginx (static+proxy), api, воркеры, elasticsearch. # Удалённо (через .env) — postgres, redis, minio, ollama, rabbitmq. # Тот же принцип, что и docker-compose.test.yml (не дублировать общую инфру), # но без bind-mount'ов исходников, без --reload, с собранным фронтендом и nginx/TLS. # Запуск: docker compose -f docker-compose.prod.yml up -d --build # ═══════════════════════════════════════════════════════════════════════════════ networks: antiplagiator: driver: bridge volumes: es_prod_data: faiss_index_prod: qdrant_prod_data: prometheus_data: grafana_data: x-app-env: &app-env env_file: .env networks: - antiplagiator restart: unless-stopped services: # ─── Локальная инфраструктура ────────────────────────────────────────────── elasticsearch: image: elasticsearch:8.13.0 container_name: antiplagiator-elasticsearch environment: - discovery.type=single-node - xpack.security.enabled=false - xpack.ml.enabled=false - ES_JAVA_OPTS=-Xms2g -Xmx2g - bootstrap.memory_lock=true - cluster.name=antiplagiator-prod ulimits: memlock: soft: -1 hard: -1 volumes: - es_prod_data:/usr/share/elasticsearch/data networks: - antiplagiator restart: unless-stopped healthcheck: test: ["CMD-SHELL", "curl -s http://localhost:9200/_cluster/health | grep -qv '\"status\":\"red\"'"] interval: 15s timeout: 10s retries: 10 start_period: 60s # Qdrant — альтернативный векторный бэкенд (снимает SPOF файлового FAISS). # Опционален: поднимается только с профилем и активируется VECTOR_BACKEND=qdrant. # docker compose -f docker-compose.prod.yml --profile qdrant up -d qdrant # docker compose -f docker-compose.prod.yml exec worker-gpu python -m app.migrate_faiss_to_qdrant # Тег сервера при необходимости поднять до версии клиента (qdrant-client 1.19). qdrant: image: qdrant/qdrant:v1.12.4 container_name: antiplagiator-qdrant profiles: ["qdrant"] volumes: - qdrant_prod_data:/qdrant/storage networks: - antiplagiator restart: unless-stopped # ─── Наблюдаемость (опционально; профиль observability) ──────────────────── # docker compose -f docker-compose.prod.yml --profile observability up -d prometheus grafana # Prometheus скрейпит api:/metrics и flower:/metrics (метрики Celery). prometheus: image: prom/prometheus:v2.54.1 container_name: antiplagiator-prometheus profiles: ["observability"] volumes: - ./infra/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro - prometheus_data:/prometheus networks: - antiplagiator restart: unless-stopped grafana: image: grafana/grafana:11.2.0 container_name: antiplagiator-grafana profiles: ["observability"] environment: - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin} - GF_USERS_ALLOW_SIGN_UP=false volumes: - ./infra/grafana/provisioning:/etc/grafana/provisioning:ro - grafana_data:/var/lib/grafana networks: - antiplagiator restart: unless-stopped depends_on: - prometheus # ─── Приложение ──────────────────────────────────────────────────────────── # RabbitMQ вынесен на отдельный сервер 192.168.20.82 (см. RABBITMQ_URL в .env) api: build: context: ./services/api dockerfile: Dockerfile container_name: antiplagiator-api <<: *app-env # CT102 (общий реверс-прокси, /etc/nginx/sites-available/academic) проксирует # /health, /api/, /ws/ напрямую на 192.168.1.32:8000 — без этого маппинга # порт нигде не публикуется и прод отвечает 502 на все API-запросы. ports: - "8000:8000" depends_on: elasticsearch: condition: service_healthy # SOCKS5-прокси (sing-box → VLESS-нода вне РФ) — OpenRouter недоступен # напрямую из России, только для этого и нужен. Остальной трафик # worker-gpu (Postgres/RabbitMQ/MinIO/Ollama) идёт напрямую, не через него. singbox-proxy: image: ghcr.io/sagernet/sing-box:v1.10.0 container_name: antiplagiator-singbox-proxy command: run -c /etc/sing-box/config.json volumes: - ./infra/singbox/config.json:/etc/sing-box/config.json:ro networks: - antiplagiator restart: unless-stopped worker-gpu: build: context: ./services/worker-gpu dockerfile: Dockerfile container_name: antiplagiator-worker-gpu <<: *app-env volumes: - faiss_index_prod:/data/index depends_on: elasticsearch: condition: service_healthy singbox-proxy: condition: service_started worker-indexer: build: context: ./services/worker-indexer dockerfile: Dockerfile container_name: antiplagiator-worker-indexer <<: *app-env volumes: - ./scripts/parsers:/parsers:ro depends_on: elasticsearch: condition: service_healthy # CORE не отвечает на ключ с российских адресов — парсер ходит через # sing-box (см. scripts/parsers/core.py) singbox-proxy: condition: service_started worker-notifier: build: context: ./services/worker-notifier dockerfile: Dockerfile container_name: antiplagiator-worker-notifier <<: *app-env worker-gost: build: context: ./services/worker-gost dockerfile: Dockerfile container_name: antiplagiator-worker-gost <<: *app-env flower: image: mher/flower:2.0 container_name: antiplagiator-flower command: celery --broker=${RABBITMQ_URL} flower --port=5555 environment: CELERY_BROKER_URL: ${RABBITMQ_URL} CELERY_RESULT_BACKEND: ${REDIS_URL} networks: - antiplagiator restart: unless-stopped # Flower не публикуется наружу напрямую — доступ через ssh-туннель при необходимости # ─── Nginx: собранный фронтенд (static) + proxy /api, /ws → api:8000, TLS ── nginx: build: context: . dockerfile: infra/nginx/Dockerfile container_name: antiplagiator-nginx networks: - antiplagiator ports: - "80:80" - "443:443" volumes: - /etc/letsencrypt:/etc/letsencrypt:ro depends_on: - api restart: unless-stopped