x-app-env: &app-env # PostgreSQL — единственная БД в Docker OPENLANE_DATABASE_URL: postgresql+psycopg2://openlane:openlane@postgres:5432/openlane_scraper OPENLANE_REDIS_URL: ${OPENLANE_REDIS_URL:-redis://redis:6379/0} CELERY_BROKER_URL: ${CELERY_BROKER_URL:-redis://redis:6379/0} CELERY_RESULT_BACKEND: ${CELERY_RESULT_BACKEND:-redis://redis:6379/0} OPENLANE_DATABASE_POOL_RECYCLE_SECONDS: ${OPENLANE_DATABASE_POOL_RECYCLE_SECONDS:-1800} CELERY_TASK_SOFT_TIME_LIMIT: ${CELERY_TASK_SOFT_TIME_LIMIT:-900} CELERY_TASK_TIME_LIMIT: ${CELERY_TASK_TIME_LIMIT:-1200} CELERY_BROKER_VISIBILITY_TIMEOUT: ${CELERY_BROKER_VISIBILITY_TIMEOUT:-2400} CELERY_BEAT_SYNC_LIMIT: ${CELERY_BEAT_SYNC_LIMIT:-0} CELERY_WORKER_MAX_TASKS_PER_CHILD: ${CELERY_WORKER_MAX_TASKS_PER_CHILD:-3} OPENLANE_MAX_CARS_LIMIT: ${OPENLANE_MAX_CARS_LIMIT:-20} OPENLANE_THROTTLE_MIN_SECONDS: ${OPENLANE_THROTTLE_MIN_SECONDS:-2.0} OPENLANE_THROTTLE_MAX_SECONDS: ${OPENLANE_THROTTLE_MAX_SECONDS:-5.0} OPENLANE_CONCURRENCY: ${OPENLANE_CONCURRENCY:-1} OPENLANE_MAX_PAGES: ${OPENLANE_MAX_PAGES:-512} OPENLANE_BROWSER_ENGINE: chromium OPENLANE_HEADLESS: "true" OPENLANE_STORAGE_STATE_FILE: /data/openlane/openlane_storage_state.json OPENLANE_RUNTIME_CONFIG_FILE: /app/runtime_config.json TZ: ${TZ:-UTC} x-env-file: &env-file - path: .env required: false x-app-service: &app-service build: . env_file: *env-file environment: *app-env volumes: - ./runtime_config.json:/app/runtime_config.json:ro x-worker-service: &worker-service <<: *app-service volumes: - ./runtime_config.json:/app/runtime_config.json:ro - tokens_data:/data - openlane_data:/data/openlane services: # PostgreSQL postgres: image: postgres:16-alpine container_name: openlane-postgres restart: unless-stopped environment: POSTGRES_USER: openlane POSTGRES_PASSWORD: openlane POSTGRES_DB: openlane_scraper ports: - "127.0.0.1:${OPENLANE_POSTGRES_PORT:-55432}:5432" volumes: - pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U openlane -d openlane_scraper"] interval: 5s timeout: 3s retries: 5 logging: driver: json-file options: max-size: "10m" max-file: "5" # Redis (Celery broker) redis: image: redis:7-alpine container_name: openlane-redis restart: unless-stopped ports: - "127.0.0.1:${OPENLANE_REDIS_PORT:-56379}:6379" healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s timeout: 3s retries: 5 command: > redis-server --appendonly yes --save 60 1000 volumes: - redisdata:/data logging: driver: json-file options: max-size: "10m" max-file: "5" # DB migrations migrate: <<: *app-service container_name: openlane-migrate restart: "no" depends_on: postgres: condition: service_healthy command: alembic upgrade head # FastAPI api: <<: *app-service container_name: openlane-api restart: unless-stopped ports: - "127.0.0.1:${OPENLANE_API_PORT:-58000}:8000" depends_on: migrate: condition: service_completed_successfully redis: condition: service_healthy command: > uvicorn openlane_scraper.api.app:app --host 0.0.0.0 --port 8000 --workers 1 healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=5)"] interval: 30s timeout: 10s retries: 3 start_period: 40s stop_grace_period: 30s logging: driver: json-file options: max-size: "10m" max-file: "5" # Celery Worker worker: <<: *worker-service container_name: openlane-worker restart: unless-stopped init: true mem_limit: ${OPENLANE_WORKER_MEM_LIMIT:-2g} memswap_limit: ${OPENLANE_WORKER_MEM_LIMIT:-2g} depends_on: migrate: condition: service_completed_successfully redis: condition: service_healthy stop_grace_period: 60s command: > celery -A openlane_scraper.worker.celery_app worker --loglevel=info --concurrency=${CELERY_WORKER_CONCURRENCY:-2} --pool=prefork --pidfile=/tmp/celery-worker.pid -Q scraping --max-tasks-per-child=${CELERY_WORKER_MAX_TASKS_PER_CHILD:-3} healthcheck: test: ["CMD-SHELL", "test -f /tmp/celery-worker.pid && kill -0 $(cat /tmp/celery-worker.pid)"] interval: 60s timeout: 10s retries: 3 start_period: 90s logging: driver: json-file options: max-size: "10m" max-file: "5" # Celery Beat (периодический планировщик) beat: <<: *worker-service container_name: openlane-beat restart: unless-stopped init: true depends_on: migrate: condition: service_completed_successfully redis: condition: service_healthy command: > celery -A openlane_scraper.worker.celery_app beat --loglevel=info --pidfile=/tmp/celery-beat.pid --schedule=/tmp/celerybeat-schedule healthcheck: test: ["CMD-SHELL", "test -f /tmp/celery-beat.pid && kill -0 $(cat /tmp/celery-beat.pid)"] interval: 60s timeout: 10s retries: 3 start_period: 90s logging: driver: json-file options: max-size: "10m" max-file: "5" openlane-auth: <<: *worker-service container_name: openlane-auth profiles: ["auth"] stdin_open: true tty: true command: > python scripts/explore_site.py volumes: pgdata: redisdata: tokens_data: openlane_data: