Docker & VNC
VoidCrawl supports two Docker△ modes: headless (production, any platform) and headful (GPU-accelerated with VNC, Linux only).
Headless Docker (production)
Chrome runs as a persistent daemon with CDP○ exposed on standard ports.
Use the published image (recommended)
Pre-built multi-arch images (linux/amd64, linux/arm64) are published to GHCR on every push to main and every tagged release:
docker run -d --network host --shm-size=2g \ ghcr.io/cascadinglabs/voidcrawl:headless-latestOr via compose, set VOIDCRAWL_IMAGE in docker/.env (see docker/.env.example) and compose will pull instead of build:
VOIDCRAWL_IMAGE=ghcr.io/cascadinglabs/voidcrawl:headless-latest \ docker compose -f docker/docker-compose.yml up -dBuild from source
docker compose -f docker/docker-compose.yml up -d --buildConnect VoidCrawl to the containerized Chrome:
import asynciofrom voidcrawl import BrowserPool, PoolConfig
async def main(): config = PoolConfig( chrome_ws_urls=["http://localhost:9222", "http://localhost:9223"], tabs_per_browser=4, ) async with BrowserPool(config) as pool: async with pool.acquire() as tab: await tab.goto("https://qscrape.dev") print(await tab.title())
asyncio.run(main())Or via environment variable:
export CHROME_WS_URLS="http://localhost:9222,http://localhost:9223"python your_script.pyHeadful Docker (GPU + VNC)
Run Chrome with a real GUI inside Docker — GPU-accelerated, isolated from your desktop, viewable via VNC.
What’s happening under the hood
Docker bundles Sway◑, Chrome, and wayvnc◇ into a single isolated container. Sway creates a virtual display in GPU memory; Chrome draws into it via Wayland★; wayvnc captures the framebuffer and streams it out over VNC. From your host you only see three things: a /dev/dri device passthrough going in, and CDP + VNC ports coming out.
Everything else (which GPU runtime to wire up, which shared memory size, which ports) is declared in docker/docker-compose.headful.yml. There’s one compose profile per GPU vendor (amd, intel, nvidia, cpu), and all runtime knobs are .env-driven. The run-headful.sh wrapper is a tiny shell script that only does GPU auto-detection before handing off to docker compose; you can skip it entirely and invoke compose directly in CI.
Quick start
# Auto-detects your GPU (AMD/Intel/NVIDIA) and starts everything./docker/run-headful.sh
# Or invoke compose directly and pick the profile yourself:COMPOSE_PROFILES=amd docker compose -f docker/docker-compose.headful.yml upCOMPOSE_PROFILES=intel docker compose -f docker/docker-compose.headful.yml upCOMPOSE_PROFILES=nvidia docker compose -f docker/docker-compose.headful.yml upCOMPOSE_PROFILES=cpu docker compose -f docker/docker-compose.headful.yml up # software renderingTo pull the published image instead of building locally, set VOIDCRAWL_IMAGE=ghcr.io/cascadinglabs/voidcrawl:headful-latest in docker/.env. The headful image is linux/amd64 only; it relies on Mesa/Sway userspace that doesn’t cross-compile cleanly.
Connecting
| Port | What | How to use |
|---|---|---|
localhost:6080 | noVNC⬡ (browser) | Open in any browser to watch Chrome |
localhost:5900 | VNC (native) | Use a VNC client (Remmina, TigerVNC) |
localhost:19222 | CDP | VoidCrawl connects here (browser 1) |
localhost:19223 | CDP | VoidCrawl connects here (browser 2) |
Connecting VoidCrawl to Docker Chrome
import asynciofrom voidcrawl import BrowserConfig, BrowserPool, PoolConfig
async def main(): config = PoolConfig( chrome_ws_urls=["http://localhost:19222", "http://localhost:19223"], tabs_per_browser=2, browser=BrowserConfig(headless=False), )
async with BrowserPool(config) as pool: async with pool.acquire() as tab: # Watch this in VNC! await tab.goto("https://en.wikipedia.org/wiki/Web_scraping") print(f"Title: {await tab.title()}")
asyncio.run(main())Viewing Chrome
Open http://localhost:6080 and click Connect. No software needed.
# TigerVNCvncviewer localhost:5900
# Or Remmina: New connection -> Protocol: VNC -> Server: localhost:5900Custom resolution
Set in docker/.env (persisted) or inline:
VNC_WIDTH=2560 VNC_HEIGHT=1440 ./docker/run-headful.sh # 2KVNC_WIDTH=1280 VNC_HEIGHT=720 ./docker/run-headful.sh # 720p (lower memory)Published images
| Tag | Platforms | Notes |
|---|---|---|
ghcr.io/cascadinglabs/voidcrawl:headless-latest | linux/amd64, linux/arm64 | Production headless Chrome + CDP. |
ghcr.io/cascadinglabs/voidcrawl:headful-latest | linux/amd64 | Sway + wayvnc + GPU headful. Linux hosts only. |
ghcr.io/cascadinglabs/voidcrawl:headless-<sha> | multi-arch | Immutable per-commit tag. Pin this in production. |
Every image is built with GHA layer caching and gated by dive against .github/dive-ci.yml. Builds that waste more than 50 MB or drop below 95 % efficiency fail CI before they’re pushed.
GPU support
| GPU | Driver | Container setup | Notes |
|---|---|---|---|
| AMD iGPU | amdgpu | /dev/dri passthrough | Works out of the box. Uses Mesa⬢ RADV. |
| AMD discrete | amdgpu | /dev/dri passthrough | Same as iGPU. |
| Intel iGPU | i915/xe | /dev/dri passthrough | Works out of the box. Uses Mesa ANV. |
| NVIDIA | nvidia | /dev/dri + --gpus all | Needs nvidia-container-toolkit + nvidia-drm.modeset=1. |
| None | — | No device passthrough | Falls back to pixman (CPU). Slower but works everywhere. |
Platform support
| Platform | Headful GPU | Headless Docker |
|---|---|---|
| Linux | Yes | Yes |
| macOS | No | Yes (Docker Desktop VM) |
| Windows | No | Yes (Docker Desktop VM) |
| WSL2 | Partial | Yes |
The headful GPU container is a Linux-only feature. It relies on /dev/dri device passthrough, Sway/wlroots, and network_mode: host.
Stopping
# Foreground: Ctrl+C# Detached:docker compose -f docker/docker-compose.headful.yml --profile amd downWhy headful?
For WAF-protected sites (Akamai, Cloudflare), headful mode is required. Headless Chrome has fundamental differences that sophisticated WAFs detect:
- Different rendering pipeline (no compositing)
- Missing screen/display properties
- HTTP/2 TLS fingerprint differences
Running headful inside Docker gives you the stealth benefits of a real Chrome GUI without it stealing focus on your desktop. See Stealth Mode for details.
FAQs
Why not just run Chrome headful natively?
Chrome headful on your desktop steals focus, pops up windows, and interferes with your work. Docker isolates everything — Chrome runs in its own display server. You can watch via VNC or ignore it entirely. Same container works in CI/CD and on remote servers.
VNC shows a black screen
Sway might not have started yet. Wait a few seconds — wayvnc auto-reconnects. Check docker logs <container> if it persists.
High memory usage with headful Chrome
Each headful Chrome instance uses ~300-500 MB more than headless because it maintains a real render tree + GPU buffers. This is expected. Reduce tab count or use the headless Docker setup if memory is constrained.
References
△ Docker. Docker, Inc. OS-level virtualization platform for packaging and running applications in containers. https://www.docker.com/
○ Chrome DevTools Protocol (CDP). Google. Protocol for instrumenting, inspecting, and debugging Chromium-based browsers. https://chromedevtools.github.io/devtools-protocol/
◑ Sway. Drew DeVault et al. i3-compatible Wayland compositor built on wlroots. https://swaywm.org/
◇ wayvnc. any1. VNC server for wlroots-based Wayland compositors. https://github.com/any1/wayvnc
★ Wayland. Wayland Contributors. Display server protocol and reference implementation, intended as a successor to the X Window System. https://wayland.freedesktop.org/
⬡ noVNC. noVNC Contributors. HTML5 VNC client that runs in any modern browser, including mobile. https://novnc.com/
⬢ Mesa. Mesa Contributors. Open-source implementation of OpenGL, Vulkan, and other graphics APIs (RADV for AMD, ANV for Intel). https://www.mesa3d.org/