Revert to headed Chrome + Xvfb; add shm_size 256m to fix Xvfb in Docker

Headless mode gets blocked by Cloudflare on the search endpoint. Headed
mode with Xvfb works on dev (Unraid). The missing piece on Proxmox LXC was
insufficient /dev/shm (Docker default 64 MB); setting shm_size: 256m gives
Xvfb enough shared memory to start.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-09 17:36:27 +00:00
parent e53c482141
commit 3c4bc633fe
3 changed files with 9 additions and 9 deletions

View file

@ -7,6 +7,7 @@ RUN apt-get update && apt-get install -y \
libpq-dev \ libpq-dev \
postgresql-client \ postgresql-client \
curl \ curl \
xvfb \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
COPY requirements.txt . COPY requirements.txt .
@ -19,4 +20,6 @@ COPY . .
EXPOSE 8000 EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] ENV DISPLAY=:99
CMD ["sh", "-c", "Xvfb :99 -screen 0 1280x720x24 -nolisten tcp & sleep 1 && uvicorn main:app --host 0.0.0.0 --port 8000"]

View file

@ -56,8 +56,8 @@ class PlaywrightLocalBackend(ScraperBackend):
Local Playwright backend using Chromium. Local Playwright backend using Chromium.
Stealth measures: Stealth measures:
- Runs headful (via xvfb in the container) headless leaks SwiftShader - Runs headful (via Xvfb in the container) headless leaks SwiftShader
WebGL, empty plugins, missing chrome.runtime WebGL, empty plugins, missing chrome.runtime; Xvfb needs shm_size 256m
- playwright-stealth patches navigator.webdriver, plugins, WebGL vendor - playwright-stealth patches navigator.webdriver, plugins, WebGL vendor
- ONE consistent modern-Chrome identity: UA + matching sec-ch-ua client - ONE consistent modern-Chrome identity: UA + matching sec-ch-ua client
hints + platform (a mismatched UA is worse than none) hints + platform (a mismatched UA is worse than none)
@ -159,17 +159,13 @@ class PlaywrightLocalBackend(ScraperBackend):
if self._playwright is None: if self._playwright is None:
self._playwright = await async_playwright().start() self._playwright = await async_playwright().start()
launch_kwargs = dict( launch_kwargs = dict(
# Headless — stealth patches + residential proxy + session headless=False,
# cookies cover the fingerprint; headed+xvfb is unreliable
# in Proxmox LXC kernels where Xvfb exits immediately.
headless=True,
args=[ args=[
'--disable-blink-features=AutomationControlled', '--disable-blink-features=AutomationControlled',
'--no-sandbox', '--no-sandbox',
'--disable-dev-shm-usage', '--disable-dev-shm-usage',
'--disable-features=IsolateOrigins,site-per-process', '--disable-features=IsolateOrigins,site-per-process',
'--disable-gpu', '--start-maximized',
'--enable-unsafe-swiftshader',
], ],
) )
# Proxy is set at launch (Chromium binds the sticky session, which # Proxy is set at launch (Chromium binds the sticky session, which

View file

@ -3,6 +3,7 @@ services:
build: ./backend build: ./backend
security_opt: security_opt:
- apparmor=unconfined - apparmor=unconfined
shm_size: '256m'
environment: environment:
- DATABASE_URL=${DATABASE_URL} - DATABASE_URL=${DATABASE_URL}
- CENTRAL_AUTH_SECRET=${CENTRAL_AUTH_SECRET} - CENTRAL_AUTH_SECRET=${CENTRAL_AUTH_SECRET}