name: whatido services: db: image: postgres:16-alpine restart: unless-stopped environment: POSTGRES_DB: whereldo POSTGRES_USER: app POSTGRES_PASSWORD: ${DB_PASSWORD} TZ: Europe/Moscow volumes: - pgdata:/var/lib/postgresql/data - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql healthcheck: test: ["CMD-SHELL", "pg_isready -U app -d whereldo"] interval: 2s timeout: 3s retries: 10 # Redis: кэш запросов, rate limit, баны IP, кэш сессий, pub/sub для SSE и воркеров. # Приложение не падает, если Redis недоступен — автоматически работает # на in-memory кэше (см. redis.js). # Отладка: docker compose exec redis redis-cli -a "$REDIS_PASSWORD" INFO redis: image: redis:7-alpine container_name: whatido-redis restart: unless-stopped command: > redis-server --requirepass ${REDIS_PASSWORD} --appendonly yes --appendfsync everysec --maxmemory ${REDIS_MAXMEMORY:-256mb} --maxmemory-policy allkeys-lru --save "" environment: TZ: Europe/Moscow REDIS_PASSWORD: ${REDIS_PASSWORD} expose: - "6379" ports: - "127.0.0.1:6379:6379" volumes: - redis-data:/data healthcheck: test: ["CMD-SHELL", "redis-cli -a \"$$REDIS_PASSWORD\" ping | grep -q PONG"] interval: 5s timeout: 3s retries: 10 start_period: 5s app: build: context: . network: host args: GIT_COMMIT: ${GIT_COMMIT:-} GIT_COMMIT_DATE: ${GIT_COMMIT_DATE:-} restart: unless-stopped extra_hosts: - "host.docker.internal:host-gateway" expose: - "3003" - "3443" ports: - "3003:3003" - "3443:3443" environment: DATABASE_URL: postgres://app:${DB_PASSWORD}@db:5432/whereldo REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379 REDIS_PREFIX: ${REDIS_PREFIX:-whatido} ADMIN_PASSWORD: ${ADMIN_PASSWORD} ADMIN_USERNAME: ${ADMIN_USERNAME:-admin} BACKUP_UPLOAD_LIMIT_MB: ${BACKUP_UPLOAD_LIMIT_MB:-500} AI_MODEL: ${AI_MODEL:-qwen2.5-1.5b-instruct-q4_k_m.gguf} AI_PROMPT: ${AI_PROMPT:-} AI_REQUEST_TIMEOUT_MS: ${AI_REQUEST_TIMEOUT_MS:-120000} PHOTO_AI_URL: ${PHOTO_AI_URL:-http://photo-ai:8080} PHOTO_AI_FACE_MODEL: ${PHOTO_AI_FACE_MODEL:-gfpgan} PHOTO_AI_FACE_TIMEOUT_MS: ${PHOTO_AI_FACE_TIMEOUT_MS:-600000} NODE_ENV: production TZ: Europe/Moscow STORAGE_DRIVER: ${STORAGE_DRIVER:-local} STORAGE_LOCAL_FALLBACK: ${STORAGE_LOCAL_FALLBACK:-1} STORAGE_KEEP_LOCAL: ${STORAGE_KEEP_LOCAL:-0} STORAGE_CACHE_MAX_AGE_HOURS: ${STORAGE_CACHE_MAX_AGE_HOURS:-168} S3_ENDPOINT: ${S3_ENDPOINT:-http://minio:9000} S3_REGION: ${S3_REGION:-us-east-1} S3_BUCKET: ${S3_BUCKET:-whatido} S3_ACCESS_KEY: ${S3_ACCESS_KEY:-whatido} S3_SECRET_KEY: ${S3_SECRET_KEY:-whatido-secret} S3_FORCE_PATH_STYLE: ${S3_FORCE_PATH_STYLE:-1} S3_PREFIX: ${S3_PREFIX:-} depends_on: db: condition: service_healthy redis: condition: service_healthy volumes: - ./uploads:/app/uploads # S3-совместимое хранилище файлов. API 9000 доступен только внутри сети compose # (плюс loopback хоста для отладки/миграции). # По умолчанию — SeaweedFS: свободный S3-сервер, доступный в Docker Hub. # Для MinIO (если образ доступен в вашем зеркале) используйте: # docker compose -f docker-compose.yml -f docker-compose.minio.yml up -d s3 # Перенос файлов из ./uploads в бакет: # docker compose exec -T app node scripts/migrate-to-s3.js --dry-run s3: image: ${S3_IMAGE:-chrislusf/seaweedfs:latest} container_name: whatido-s3 restart: unless-stopped command: server -dir=/data -s3 -s3.port=9000 environment: AWS_ACCESS_KEY_ID: ${S3_ACCESS_KEY:-whatido} AWS_SECRET_ACCESS_KEY: ${S3_SECRET_KEY:-whatido-secret} TZ: Europe/Moscow expose: - "9000" ports: - "127.0.0.1:9000:9000" volumes: - s3-data:/data # Публикация через Tailscale (Serve / Funnel) без проброса портов. # Приложение доступно по https://whatido..ts.net # tailscale: # image: tailscale/tailscale:latest # hostname: whatido # restart: unless-stopped # network_mode: host # cap_add: # - NET_ADMIN # - SYS_MODULE # environment: # SSL_CERT_FILE: /etc/tailscale/app-certs/cert.pem # volumes: # - /var/lib/tailscale:/var/lib/tailscale # - /dev/net/tun:/dev/net/tun # - /lib/modules:/lib/modules:ro # - ./certs:/etc/tailscale/app-certs:ro # - ./start-tailscale.sh:/start-tailscale.sh:ro # command: ["/bin/sh", "/start-tailscale.sh"] # depends_on: # - app # Публикация наружу через Cloudflare Tunnel (cloudflared), Quick Tunnel: # случайный публичный URL *.trycloudflare.com, который печатается в логах: # docker compose logs -f cloudflared # Сборка из Dockerfile.cloudflared добавляет опциональный WireGuard: # если в wg/wg0.conf лежит конфиг — контейнер сначала поднимает VPN и # только потом запускает туннель (исход Cloudflare через VPN). Без конфига # туннель стартует сразу, как в базовой схеме. # cloudflared: # build: # context: . # dockerfile: Dockerfile.cloudflared # restart: unless-stopped # cap_add: # - NET_ADMIN # privileged: true # volumes: # - ./wg:/etc/wireguard:ro # environment: # TZ: Europe/Moscow # CLOUDFLARE_TUNNEL_URL: ${CLOUDFLARE_TUNNEL_URL:-http://app:3003} # WG_HANDSHAKE_TIMEOUT: ${WG_HANDSHAKE_TIMEOUT:-60} # depends_on: # - app text-corrector: image: ghcr.io/ggml-org/llama.cpp:server container_name: text-corrector restart: unless-stopped entrypoint: ["/bin/sh", "/start-text-corrector.sh"] environment: AI_MODEL: ${AI_MODEL:-qwen2.5-1.5b-instruct-q4_k_m.gguf} AI_MODEL_REPO: ${AI_MODEL_REPO:-Qwen/Qwen2.5-1.5B-Instruct-GGUF} HUGGINGFACE_TOKEN: ${HUGGINGFACE_TOKEN:-} volumes: - ./models:/models - ./start-text-corrector.sh:/start-text-corrector.sh:ro ports: - "8080:8080" # ИИ-улучшение фотографий (Real-ESRGAN: апскейл, денойз, восстановление лиц GFPGAN). # Устройство выбирается автоматически (PHOTO_AI_DEVICE=auto): CUDA, если контейнеру # выдан GPU, иначе CPU. Запуск на GPU — через docker-compose.gpu.yml. # Поднимается вместе со стеком; если не нужен — PHOTO_AI_URL пустой в .env. # Порт 8081 пробрасывается только на loopback хоста — наружу ничего не публикуется, # хостовый 8080 уже занят text-corrector. Ручные проверки: curl http://127.0.0.1:8081/health photo-ai: build: ./photo-ai container_name: photo-ai restart: unless-stopped environment: MODEL_PATH: /models/RealESRGAN_x2plus.pth PHOTO_AI_MODELS_DIR: /models PHOTO_AI_MAX_PIXELS: ${PHOTO_AI_MAX_PIXELS:-4000000} PHOTO_AI_DEVICE: ${PHOTO_AI_DEVICE:-auto} PHOTO_AI_TILE: ${PHOTO_AI_TILE:-256} PHOTO_AI_FACE_MODEL: ${PHOTO_AI_FACE_MODEL:-gfpgan} PHOTO_AI_LOAD_ALL: ${PHOTO_AI_LOAD_ALL:-0} PHOTO_AI_JPEG_QUALITY: ${PHOTO_AI_JPEG_QUALITY:-92} TZ: Europe/Moscow volumes: - photo-ai-models:/models ports: - "127.0.0.1:8081:8080" healthcheck: test: ["CMD", "python", "-c", "import sys, urllib.request; r = urllib.request.urlopen('http://127.0.0.1:8080/health', timeout=5); sys.exit(0 if r.status == 200 else 1)"] interval: 30s timeout: 10s retries: 5 start_period: 300s volumes: pgdata: photo-ai-models: redis-data: s3-data: