Skip to content
Cascading Labs QScrape VoidCrawl Yosoi

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.

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-latest

Or 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 -d

Build from source

docker compose -f docker/docker-compose.yml up -d --build

Connect VoidCrawl to the containerized Chrome:

import asyncio
from 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.py

Headful 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.

Docker Container
SwayWayland compositor
Creates a virtual screen in GPU memory. Chrome draws its windows here.
draws into
Chromeheadful · GPU-accelerated
Renders into Sway's virtual screen via --ozone-platform=wayland.
captures from
wayvncVNC server
Captures Sway's framebuffer and streams it to VNC clients.
/dev/driGPU passthrough
19222 · 19223CDP
5900VNC

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 up
COMPOSE_PROFILES=intel docker compose -f docker/docker-compose.headful.yml up
COMPOSE_PROFILES=nvidia docker compose -f docker/docker-compose.headful.yml up
COMPOSE_PROFILES=cpu docker compose -f docker/docker-compose.headful.yml up # software rendering

To 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

PortWhatHow to use
localhost:6080noVNC (browser)Open in any browser to watch Chrome
localhost:5900VNC (native)Use a VNC client (Remmina, TigerVNC)
localhost:19222CDPVoidCrawl connects here (browser 1)
localhost:19223CDPVoidCrawl connects here (browser 2)

Connecting VoidCrawl to Docker Chrome

import asyncio
from 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.

Custom resolution

Set in docker/.env (persisted) or inline:

VNC_WIDTH=2560 VNC_HEIGHT=1440 ./docker/run-headful.sh # 2K
VNC_WIDTH=1280 VNC_HEIGHT=720 ./docker/run-headful.sh # 720p (lower memory)

Published images

TagPlatformsNotes
ghcr.io/cascadinglabs/voidcrawl:headless-latestlinux/amd64, linux/arm64Production headless Chrome + CDP.
ghcr.io/cascadinglabs/voidcrawl:headful-latestlinux/amd64Sway + wayvnc + GPU headful. Linux hosts only.
ghcr.io/cascadinglabs/voidcrawl:headless-<sha>multi-archImmutable 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

GPUDriverContainer setupNotes
AMD iGPUamdgpu/dev/dri passthroughWorks out of the box. Uses Mesa RADV.
AMD discreteamdgpu/dev/dri passthroughSame as iGPU.
Intel iGPUi915/xe/dev/dri passthroughWorks out of the box. Uses Mesa ANV.
NVIDIAnvidia/dev/dri + --gpus allNeeds nvidia-container-toolkit + nvidia-drm.modeset=1.
NoneNo device passthroughFalls back to pixman (CPU). Slower but works everywhere.

Platform support

PlatformHeadful GPUHeadless Docker
LinuxYesYes
macOSNoYes (Docker Desktop VM)
WindowsNoYes (Docker Desktop VM)
WSL2PartialYes

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 down

Why 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/