中文 | English
Headless browser rendering API with stealth capabilities — self-hostable via Docker or pip.
VeilRender accepts a URL and returns the fully rendered page content (HTML, Markdown, readability-extracted article) using a stealth Chromium browser. Designed as a fallback for fetch tools that fail on JavaScript-rendered or bot-protected pages.
- Stealth rendering — CloakBrowser (71 C++ fingerprint patches) + Patchright (stealth Playwright fork)
- Multiple output formats — raw HTML, Markdown, readability-extracted article text
- Screenshot capture — full-page, viewport, or element; PNG or JPEG with quality/scale control
- Horizontal scaling — gateway + remote browser worker pool with health checks and auto-reconnection
- Mixed browser backends — Chromium (CDP) and Firefox/Camoufox (Playwright protocol) in the same pool
- Prometheus metrics —
/metricsendpoint with latency percentiles, per-worker gauges - Dashboard — live stats at
/with i18n (en/zh), SVG capacity gauge - Ad/tracker blocking — 82k domain blocklist from StevenBlack/hosts
- Render cache — L1 in-memory + L2 S3-compatible (R2, Oracle, AWS)
- CDP proxy — direct WebSocket access to the browser at
/cdp - Zero external deps (except
patchright) — HTTP server, S3 client, HTML parsing all vendored
# Single instance with embedded CloakBrowser
docker run -p 7860:7860 -e VEILRENDER_API_TOKEN=your-secret oaklight/veilrender:latest
# Gateway only (no browser, 336MB)
docker run -p 7860:7860 oaklight/veilrender:gatewaypip install veilrender
veilrender # starts on :7860, auto-downloads CloakBrowser binary on first run# docker-compose.yaml
services:
veilrender:
image: oaklight/veilrender:gateway
ports: ["7860:7860"]
environment:
- VEILRENDER_WORKERS=cdp://browser-worker:9222
browser-worker:
image: cloakhq/cloakbrowser
command: ["cloakserve"]See deploy/ for more compose examples including mixed Chromium+Camoufox pools.
{"status": "ok"}Render a URL and return the page content.
curl -X POST http://localhost:7860/render \
-H "Authorization: Bearer your-secret" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com", "formats": ["html", "readability", "markdown"]}'Request body:
| Field | Type | Default | Description |
|---|---|---|---|
url |
string | (required) | URL to render |
formats |
string[] | ["html"] |
Output formats: html, readability, markdown |
wait_until |
string | "load" |
Playwright wait strategy: load, domcontentloaded, networkidle |
Capture a screenshot (PNG or JPEG).
Request body:
| Field | Type | Default | Description |
|---|---|---|---|
url |
string | (required) | URL to capture |
format |
string | "png" |
Image format: png, jpeg |
quality |
int | — | JPEG quality 0–100 (only for jpeg) |
full_page |
bool | false |
Capture the full scrollable page |
scale |
float | — | Device pixel ratio (e.g. 2 for retina) |
selector |
string | — | CSS selector to screenshot a specific element |
clip |
object | — | Region to capture: {"x": N, "y": N, "width": N, "height": N} |
color_scheme |
string | — | "light", "dark", or "no-preference" |
wait_for |
string | — | CSS selector to wait for before capture |
transparent |
bool | false |
Transparent background (PNG only) |
wait_until |
string | "networkidle" |
Playwright wait strategy |
timeout |
int | — | Timeout in ms |
viewport_width |
int | — | Viewport width in px |
viewport_height |
int | — | Viewport height in px |
# Default PNG screenshot
curl -X POST http://localhost:7860/screenshot \
-H "Authorization: Bearer your-secret" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com"}' -o screenshot.png
# JPEG with quality
curl -X POST http://localhost:7860/screenshot \
-H "Authorization: Bearer your-secret" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com", "format": "jpeg", "quality": 80}' \
-o screenshot.jpg
# Element screenshot
curl -X POST http://localhost:7860/screenshot \
-H "Authorization: Bearer your-secret" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com", "selector": "#main-content"}' \
-o element.pngPrometheus exposition format with uptime, request counters, cache stats, latency summaries (p50/p95), and per-worker health gauges.
JSON API for live dashboard data (polled by the dashboard UI).
Direct CDP WebSocket proxy to the browser. Supports ?worker=N for targeting specific workers in a pool.
All settings via VEILRENDER_* environment variables:
| Variable | Default | Description |
|---|---|---|
VEILRENDER_API_TOKEN |
(none) | Bearer token. If unset, auth is disabled |
VEILRENDER_PORT |
7860 |
Server port |
VEILRENDER_HOST |
0.0.0.0 |
Bind address |
VEILRENDER_TIMEOUT |
30000 |
Page load timeout (ms) |
VEILRENDER_VIEWPORT_WIDTH |
1280 |
Browser viewport width |
VEILRENDER_VIEWPORT_HEIGHT |
720 |
Browser viewport height |
VEILRENDER_MAX_CONCURRENT |
5 |
Max concurrent pages (single-instance) |
| Variable | Default | Description |
|---|---|---|
VEILRENDER_WORKERS |
(none) | Comma-separated worker endpoints (enables pool mode) |
VEILRENDER_WORKER_MAX_CONCURRENT |
5 |
Per-worker page concurrency |
VEILRENDER_WORKER_HEALTH_INTERVAL |
10 |
Health check interval (seconds) |
Worker endpoint formats:
cdp://host:9222orhttp://host:9222— Chromium CDP workerplaywright://host:1234/ws-path— Firefox/Camoufox Playwright workerplaywrights://host:1234/ws-path— TLS Playwright worker
| Variable | Default | Description |
|---|---|---|
CLOAKBROWSER_BINARY |
(auto) | Path to custom browser binary |
CLOAKBROWSER_MIRROR |
(none) | GitHub mirror URL for China (e.g. https://ghfast.top) |
Binary detection cascade: env var → ~/.cloakbrowser/*/chrome → auto-download from GitHub Releases.
| Variable | Default | Description |
|---|---|---|
VEILRENDER_CACHE_ENABLED |
false |
Enable render caching |
VEILRENDER_CACHE_TTL |
86400 |
Cache TTL in seconds |
VEILRENDER_RESOURCE_FILTER |
true |
Block ads/trackers during rendering |
VEILRENDER_S3_ENDPOINT |
(none) | S3 endpoint for L2 cache |
VEILRENDER_S3_ACCESS_KEY |
(none) | S3 access key |
VEILRENDER_S3_SECRET_KEY |
(none) | S3 secret key |
Embedded CloakBrowser — simplest setup, no external dependencies.
docker run -p 7860:7860 -e VEILRENDER_API_TOKEN=secret oaklight/veilrender:latestBrowser containers scale independently. Gateway routes via least-connections.
services:
veilrender:
image: oaklight/veilrender:gateway
environment:
- VEILRENDER_WORKERS=cdp://worker1:9222,cdp://worker2:9222
worker1:
image: cloakhq/cloakbrowser
command: ["cloakserve"]
worker2:
image: cloakhq/cloakbrowser
command: ["cloakserve"]Dual-engine stealth: CloakBrowser for Chromium-fingerprinted sites, Camoufox for Firefox-fingerprinted sites.
services:
veilrender:
image: oaklight/veilrender:gateway
environment:
- VEILRENDER_WORKERS=cdp://chromium:9222,playwright://camoufox:1234/ws
chromium:
image: cloakhq/cloakbrowser
command: ["cloakserve"]
camoufox:
build: deploy/Dockerfile.camoufoxConnect the gateway to your desktop browser to render authenticated pages using your existing login sessions. Start Chromium with CDP enabled:
chromium --remote-debugging-port=9333Then run the gateway container:
docker run --rm --network host \
-e VEILRENDER_PORT=7880 \
-e VEILRENDER_WORKERS=http://127.0.0.1:9333 \
oaklight/veilrender:gatewayNow POST /render to localhost:7880 renders pages as your logged-in browser
would — including sites behind authentication, Cloudflare challenges, etc.
Tip: Create a desktop shortcut for CDP-enabled Chromium so you can launch it from your application menu when needed. See AGENTS.md for details.
Note: Only Chromium-based browsers support CDP. Firefox/Waterfox cannot be used this way (they lack the Juggler protocol that Playwright requires). For Firefox-based stealth rendering, use Camoufox workers.
| Tag | Size | Content |
|---|---|---|
latest / 0.4.0 |
1.07GB | Full — Patchright + CloakBrowser |
gateway |
336MB | Gateway only — no browser binary |
Build locally:
make build # full image
make build-gateway # gateway image# Setup
pip install -e ".[dev]"
# Run locally
make dev # starts on :7860
# Lint & type check
make lint # ruff check --fix && ruff format
make typecheck # ty check
# Dev deploy
make deploy-dev SSH_TARGET=oaklight.buttercupFeel free to discuss the project via GitHub Issues or the LINUX DO community.
MIT
