| name | patchright-docker |
| description | Stealth browser in Docker via Patchright (undetected Playwright). Bypasses Cloudflare, bot detection, and headless fingerprinting. No Chrome popup. Use when curl/webfetch gets blocked (403, Cloudflare challenge, "Access Denied") or when you need to interact with a page that requires a real browser. Triggers on "Cloudflare blocked", "403 from curl", "stealth fetch", "scrape protected page", "patchright", or any URL that returns a Cloudflare challenge. |
Patchright Docker
Stealth browser in Docker. Patchright (undetected Playwright fork) + xvfb. Bypasses Cloudflare, bot detection, and headless browser fingerprinting without opening a Chrome window.
When to Use
webfetch or curl returns 403, Cloudflare challenge, or "Access Denied"
- A site requires JavaScript rendering that webfetch can't handle
- You need to interact with a page (click, fill, scroll) before extracting data
- You need a screenshot of a protected page
When NOT to Use
- URL works fine with
webfetch or curl (use those first, they're faster)
- Complex multi-step automation with login, 2FA, or session management (use dedicated scripts like SW or AA)
- You need to run many requests quickly (each invocation starts a fresh browser, ~10-15s overhead)
Prerequisites
docker pull ghcr.io/borski/patchright-docker
Fetch Mode (Default)
Pass a URL and get back page content. Stealth curl replacement.
docker run --rm ghcr.io/borski/patchright-docker "https://example.com"
docker run --rm ghcr.io/borski/patchright-docker "https://example.com" --selector "table"
docker run --rm ghcr.io/borski/patchright-docker "https://example.com" --selector "h1" --selector ".content"
docker run --rm ghcr.io/borski/patchright-docker "https://example.com" --selector "table" --html
docker run --rm ghcr.io/borski/patchright-docker "https://example.com" --json
docker run --rm ghcr.io/borski/patchright-docker "https://example.com" --js "document.title"
Cloudflare Bypass
Use --warmup to visit the site's homepage first. This establishes cookies and passes Cloudflare challenges before navigating to the protected page.
docker run --rm ghcr.io/borski/patchright-docker \
"https://protected-site.com/data" \
--warmup "https://protected-site.com"
Interactions Before Extraction
Click buttons, fill forms, then extract the result.
docker run --rm ghcr.io/borski/patchright-docker "https://example.com" \
--click "#load-more" --wait 3000 --selector ".results"
docker run --rm ghcr.io/borski/patchright-docker "https://example.com" \
--fill "#search" "query text" --click "#submit" --wait 3000 --selector ".results"
Screenshots
docker run --rm -v ./output:/output ghcr.io/borski/patchright-docker \
"https://example.com" --screenshot /output/page.png
docker run --rm -v ./output:/output ghcr.io/borski/patchright-docker \
"https://example.com" --screenshot /output/page.png --full-page
Script Mode
Mount any Python script for complex automation. The container has Patchright and Chromium pre-installed. xvfb is already running.
docker run --rm -v ./my_script.py:/scripts/my_script.py \
ghcr.io/borski/patchright-docker script /scripts/my_script.py [args...]
Your script can use the full Patchright API:
from patchright.sync_api import sync_playwright
import tempfile
with sync_playwright() as p:
ctx = p.chromium.launch_persistent_context(
user_data_dir=tempfile.mkdtemp(),
headless=False,
viewport={"width": 1440, "height": 900},
)
page = ctx.pages[0] if ctx.pages else ctx.new_page()
page.goto("https://example.com")
print(page.title())
ctx.close()
All Options
| Option | Description |
|---|
--selector SEL | CSS selector to extract. Repeatable. Default: body |
--html | Return inner HTML instead of text |
--json | JSON output: {title, url, selectors, content} |
--screenshot PATH | Save screenshot (mount volume for output) |
--full-page | Full page screenshot |
--wait MS | Wait after load, milliseconds. Default: 5000 |
--timeout MS | Navigation timeout. Default: 30000 |
--warmup URL | Visit URL first to establish session (Cloudflare bypass) |
--js EXPR | Execute JavaScript, return result |
--click SEL | Click selector before extracting. Repeatable. |
--fill SEL VALUE | Fill input before extracting. Repeatable. |
--user-agent STR | Override user agent |
Common Patterns
Cloudflare-protected table
docker run --rm ghcr.io/borski/patchright-docker \
"https://seats.aero/tools/releases?source=aeroplan" \
--warmup "https://seats.aero" \
--selector "table" 2>/dev/null
JSON + jq pipeline
docker run --rm ghcr.io/borski/patchright-docker \
"https://example.com" --json 2>/dev/null | jq -r '.content'
Multiple selectors combined
docker run --rm ghcr.io/borski/patchright-docker \
"https://example.com" \
--selector "h1" --selector "article" --json 2>/dev/null
Performance
- ~10-15 seconds per invocation (browser startup + page load + Cloudflare challenge if present)
- Each run creates a fresh browser context (no cookie persistence between runs)
- Redirect stderr to
/dev/null to suppress Chromium/xvfb noise: 2>/dev/null
- For repeated access to the same site, a custom script with session reuse will be faster
Troubleshooting
- "Cloudflare challenge did not resolve": Some sites have aggressive bot detection that even Patchright can't bypass. Try adding
--wait 10000 for more time, or --user-agent with a realistic string.
- Timeout: Increase
--timeout 60000 for slow pages.
- Empty output: The selector might not exist. Try without
--selector first to see the full page text, then narrow down.
- stderr noise: Chromium and xvfb print warnings to stderr. Pipe
2>/dev/null for clean output.