# All-in-one Dockerfile: Playwright API + bundled turnstile-solver. # This is what HF Space builds (single container). # For VPS, prefer `docker compose up` which uses solver/Dockerfile + # this same file but with multi-service split (see docker-compose.yml). FROM mcr.microsoft.com/playwright:v1.49.0-jammy USER root # --- Step 1: essential packages --- RUN apt-get update \ && apt-get install -y --no-install-recommends \ util-linux procps psmisc ca-certificates curl unzip wget gnupg \ python3 python3-pip python3-venv \ && rm -rf /var/lib/apt/lists/* # --- Step 2: fonts (best-effort) --- RUN apt-get update && \ for pkg in fonts-liberation fonts-noto fonts-noto-color-emoji fonts-noto-cjk \ fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg \ fonts-dejavu-core; do \ apt-get install -y --no-install-recommends "$pkg" || echo "skip: $pkg"; \ done && \ rm -rf /var/lib/apt/lists/* # --- Step 3: Xvfb + GTK/X11 deps for Camoufox (used by the solver) --- RUN apt-get update && \ apt-get install -y --no-install-recommends \ xvfb x11-utils dbus-x11 dumb-init \ libgtk-3-0 libdbus-glib-1-2 libxt6 libasound2 \ libx11-xcb1 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 \ libpango-1.0-0 libnss3 libatk1.0-0 libatk-bridge2.0-0 \ libcups2 libxkbcommon0 libgbm1 libglib2.0-0 libexpat1 \ || echo "Xvfb/GTK stack partial — solver may degrade" && \ rm -rf /var/lib/apt/lists/* # --- Step 4: Google Chrome stable (best-effort) --- RUN set -eux; \ ( \ wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | gpg --dearmor -o /usr/share/keyrings/google-chrome.gpg && \ echo 'deb [signed-by=/usr/share/keyrings/google-chrome.gpg] https://dl.google.com/linux/chrome/deb/ stable main' > /etc/apt/sources.list.d/google-chrome.list && \ apt-get update && \ apt-get install -y --no-install-recommends google-chrome-stable \ ) || echo "Google Chrome install failed — channel:'chrome' will be unavailable, chromium still works"; \ rm -rf /var/lib/apt/lists/* ENV PORT=7860 \ SOLVER_PORT=9988 \ TIMEOUT_MS=1800000 \ HOME=/home/pwuser \ BUN_INSTALL=/home/pwuser/.bun \ PATH=/home/pwuser/.bun/bin:/usr/local/bin:/usr/bin:/bin \ PLAYWRIGHT_BROWSERS_PATH=/ms-playwright \ PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 \ NODE_PATH=/app/node_modules:/app/helpers \ TRANSFORMERS_CACHE=/app/.cache/transformers \ TESSDATA_PREFIX=/app/tesseract-data \ COOKIES_DIR=/app/cookies \ CAMOUFOX_HEADLESS=virtual \ PYTHONUNBUFFERED=1 \ TURNSTILE_SOLVER_URL=http://127.0.0.1:9988 WORKDIR /app RUN mkdir -p /app/runs /app/helpers /app/public /app/cookies \ /app/.cache/transformers /app/tesseract-data \ /app/solver /home/pwuser \ && chown -R 1000:1000 /app /home/pwuser # --- Solver: install Python deps as root (needs system site-packages access) --- COPY --chown=1000:1000 solver/requirements.txt /app/solver/requirements.txt RUN pip3 install --no-cache-dir --break-system-packages \ -r /app/solver/requirements.txt \ && python3 -m camoufox fetch \ || echo "[warn] solver dep install partial — /solve may degrade" USER 1000 # --- Bun for the Playwright API server --- RUN curl -fsSL https://bun.sh/install | bash COPY --chown=1000:1000 package.json ./ RUN bun install --production --ignore-scripts # --- Pre-fetch CAPTCHA models so first request is fast --- RUN bun -e 'try { \ const { pipeline, env } = await import("@huggingface/transformers"); \ env.cacheDir = "/app/.cache/transformers"; \ console.log("[prefetch] whisper-tiny.en..."); \ await pipeline("automatic-speech-recognition", "Xenova/whisper-tiny.en", { quantized: true }); \ console.log("[prefetch] whisper done"); \ } catch (e) { console.warn("[prefetch] whisper skipped:", e.message); }' \ || echo "whisper prefetch skipped" RUN cd /app/tesseract-data && \ (curl -fsSLO https://github.com/tesseract-ocr/tessdata_fast/raw/main/eng.traineddata \ && echo "[prefetch] tesseract eng.traineddata done") \ || echo "tesseract prefetch skipped" # --- Application code --- COPY --chown=1000:1000 helpers/ ./helpers/ COPY --chown=1000:1000 public/ ./public/ COPY --chown=1000:1000 solver/solver.py solver/service.py solver/entrypoint.sh ./solver/ COPY --chown=1000:1000 solver/web ./solver/web COPY --chown=1000:1000 server.js ./ COPY --chown=1000:1000 supervisor.sh ./ USER root RUN chmod +x /app/solver/entrypoint.sh /app/supervisor.sh \ && chown -R 1000:1000 /app USER 1000 # --- Sanity check --- RUN test -f /app/server.js \ && test -f /app/supervisor.sh \ && test -f /app/solver/service.py \ && test -f /app/public/index.html \ && test -d /app/helpers/stealth \ && test -d /app/helpers/captcha \ && test -f /app/node_modules/playwright/package.json \ && echo "build artifacts OK" EXPOSE 7860 # Supervisor launches the solver in the background, then the API in the # foreground. If either dies the whole container exits and HF/Docker # restarts it. CMD ["bash", "/app/supervisor.sh"]