services: # ---------- Self-hosted Turnstile/CF solver (no third-party API) ---------- turnstile-solver: build: ./solver container_name: turnstile-solver restart: unless-stopped environment: PORT: 9988 MAX_WORKERS: ${SOLVER_WORKERS:-8} CAMOUFOX_HEADLESS: virtual # Solver does not need to be exposed to the host — playwright-api reaches # it over the internal Docker network. Uncomment the ports stanza below # if you want to test it directly. # ports: # - "9988:9988" shm_size: "1gb" healthcheck: test: ["CMD-SHELL", "curl -fsS http://localhost:9988/health > /dev/null || exit 1"] interval: 30s timeout: 5s retries: 5 start_period: 60s logging: driver: json-file options: max-size: "20m" max-file: "5" networks: - internal # ---------- Playwright execution API ---------- playwright-api: build: . container_name: playwright-api restart: unless-stopped depends_on: turnstile-solver: condition: service_healthy ports: - "${HOST_PORT:-7860}:7860" environment: PORT: 7860 TIMEOUT_MS: ${TIMEOUT_MS:-1800000} # Wired to the bundled solver — no external URL, no API key. TURNSTILE_SOLVER_URL: http://turnstile-solver:9988 WHISPER_MODEL: ${WHISPER_MODEL:-Xenova/whisper-tiny.en} COOKIES_DIR: /app/cookies shm_size: "2gb" ipc: host deploy: resources: limits: cpus: "${CPU_LIMIT:-4.0}" memory: "${MEM_LIMIT:-8g}" healthcheck: test: ["CMD-SHELL", "curl -fsS http://localhost:7860/health > /dev/null || exit 1"] interval: 30s timeout: 5s retries: 3 start_period: 30s # tmpfs mounts default to root:root,755 which the UID-1000 container user # can't write to. Pin uid/gid to 1000 and 0700 so /app/runs is writable. tmpfs: - /tmp:size=2g,exec,uid=1000,gid=1000,mode=1777 - /app/runs:size=512m,exec,uid=1000,gid=1000,mode=0700 volumes: - playwright-cookies:/app/cookies logging: driver: json-file options: max-size: "20m" max-file: "5" networks: - internal networks: internal: driver: bridge volumes: playwright-cookies: