Overview
Running browser automation in Docker has traditionally been challenging, especially with GPU acceleration. Zendriver includes first-class Docker support through the zendriver-docker project template, making deployment straightforward.
Zendriver’s Docker solution runs a real, GPU-accelerated browser (not headless) inside containers using Wayland and VNC for remote access.
Why Docker with Zendriver?
Production ready Deploy browser automation reliably across different environments
GPU acceleration Full hardware acceleration for realistic browser behavior
VNC debugging See what the browser is doing in real-time via VNC
Isolation Separate browser instances with clean profiles
Quick start
Clone the template
git clone https://github.com/cdpdriver/zendriver-docker.git
cd zendriver-docker
Configure render group
Find your render group GID (for GPU access): stat -c "%g" /dev/dri/renderD128
Update docker-compose.yml with the GID: environment :
- RENDER_GROUP_GID=107 # Use your GID here
Build and run
docker compose build
docker compose up
Connect with VNC
Connect to localhost:5911 using any VNC client:
Username : wayvnc
Password : wayvnc
You’ll see the browser running in real-time!
Docker Compose configuration
Here’s the complete docker-compose.yml structure:
services :
zendriver :
build :
context : .
dockerfile : Dockerfile
ports :
- 5911:5911 # VNC server port
volumes :
- ./zendriver:/app/zendriver
- ./scripts:/app/scripts
- ./tests:/app/tests
environment :
# Render group for GPU access (required)
- RENDER_GROUP_GID=107
# Wayland/Sway configuration
- SWAY_RESOLUTION=1920x1080
# VNC settings
- WAYVNC_PORT=5911
- WAYVNC_ENABLE_AUTH=true
- WAYVNC_USERNAME=wayvnc
- WAYVNC_PASSWORD=wayvnc
# Debug: Pause after tests for inspection
- ZENDRIVER_PAUSE_AFTER_TEST=false
# Required for GPU access
privileged : true
volumes :
wayvnc-certs :
Environment variables
The GID of the group that owns /dev/dri/renderD128. Find it with: stat -c "%g" /dev/dri/renderD128
SWAY_RESOLUTION
string
default: "1920x1080"
The resolution of the virtual display. Common values: 1920x1080, 1280x720, 2560x1440
The port where VNC server will listen
Enable VNC authentication. Set to false for testing (not recommended in production)
VNC authentication username
VNC authentication password. Change this in production!
ZENDRIVER_PAUSE_AFTER_TEST
Pause after each test completes. Useful for debugging. Resume with Mod+Return in VNC.
Dockerfile structure
The Zendriver Dockerfile is based on a specialized Wayland+Chrome+VNC base image:
FROM ghcr.io/stephanlensky/swayvnc-chrome:latest
ENV PYTHONUNBUFFERED=1
# Install uv package manager
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
ENV UV_COMPILE_BYTECODE=1
ENV UV_LINK_MODE=copy
# Create app directory
RUN mkdir /app
RUN chown $DOCKER_USER:$DOCKER_USER /app
# Switch to non-root user
USER $DOCKER_USER
# Configure sway hotkey for test debugging
RUN echo 'bindsym Mod4+Return exec /app/tests/next_test.sh' >> ~/.config/sway/config
WORKDIR /app
# Install Python 3.13
RUN uv python install 3.13
# Install dependencies
COPY --chown=$DOCKER_USER:$DOCKER_USER pyproject.toml uv.lock /app/
RUN --mount=type=cache,target=/home/$DOCKER_USER/.cache/uv,uid=$PUID,gid=$PGID \
uv sync --frozen --no-install-project
# Copy and install project
COPY --chown=$DOCKER_USER:$DOCKER_USER . /app
ENV PATH= "/app/.venv/bin:$PATH"
RUN --mount=type=cache,target=/home/$DOCKER_USER/.cache/uv,uid=$PUID,gid=$PGID \
uv sync --frozen
USER root
ENTRYPOINT [ "/entrypoint.sh" , "./scripts/test.sh" ]
Running Zendriver in Docker
Once your container is running, your Zendriver scripts work the same as locally:
# app.py
import asyncio
import zendriver as zd
async def main ():
browser = await zd.start()
page = await browser.get( "https://www.browserscan.net/bot-detection" )
await page.save_screenshot( "/app/output/browserscan.png" )
await browser.stop()
if __name__ == "__main__" :
asyncio.run(main())
Run it in the container:
docker compose run zendriver python app.py
VNC debugging
Connect to the container with a VNC client to see the browser in action:
# Use built-in VNC client
open vnc://localhost:5911
# Or use RealVNC Viewer
# Download from: https://www.realvnc.com/en/connect/download/viewer/
# Install a VNC client
sudo apt install tigervnc-viewer
# Connect
vncviewer localhost:5911
# Download and install RealVNC Viewer
# https://www.realvnc.com/en/connect/download/viewer/
# Connect to localhost:5911
Use VNC debugging during development to:
Verify challenges are being solved correctly
Debug element interactions
Check page rendering issues
Monitor browser behavior in real-time
Production considerations
Security
Change VNC password
Update WAYVNC_PASSWORD in docker-compose.yml or use secrets: environment :
- WAYVNC_PASSWORD_FILE=/run/secrets/vnc_password
secrets :
- vnc_password
secrets :
vnc_password :
file : ./secrets/vnc_password.txt
Disable VNC in production
If you don’t need VNC access in production: environment :
- WAYVNC_ENABLE_AUTH=false
ports : [] # Remove port mapping
Use non-privileged mode
The container needs privileged: true for GPU access. In environments without GPU: privileged : false
environment :
- LIBGL_ALWAYS_SOFTWARE=1 # Software rendering
Resource limits
services :
zendriver :
# ... other config ...
deploy :
resources :
limits :
cpus : '2.0'
memory : 4G
reservations :
cpus : '1.0'
memory : 2G
Persistent storage
Mount volumes for persistent data:
volumes :
- ./output:/app/output # Screenshots, downloads
- ./profiles:/app/profiles # Browser profiles
- ./logs:/app/logs # Application logs
Multi-container deployments
Run multiple isolated browser instances:
services :
zendriver-1 :
build : .
ports :
- "5911:5911"
environment :
- RENDER_GROUP_GID=107
- INSTANCE_ID=1
volumes :
- ./output/instance1:/app/output
zendriver-2 :
build : .
ports :
- "5912:5911" # Different port
environment :
- RENDER_GROUP_GID=107
- INSTANCE_ID=2
volumes :
- ./output/instance2:/app/output
Linux only : Zendriver’s Docker solution currently only works on Linux hosts due to GPU passthrough requirements.macOS and Windows users can:
Use WSL2 (Windows) with GPU passthrough
Run without GPU acceleration (slower, more detectable)
Deploy to Linux servers for production
Checking GPU support
# Check if GPU is available
ls -la /dev/dri/
# Should show renderD128 or similar
crw-rw----+ 1 root render 226, 128 Nov 15 10:30 renderD128
Troubleshooting
Permission denied on /dev/dri
Make sure RENDER_GROUP_GID matches your system: stat -c "%g" /dev/dri/renderD128
Update the value in docker-compose.yml.
Check the container is running: docker compose ps
Verify port mapping: docker compose port zendriver 5911
Check firewall rules if connecting remotely
The Wayland session may not have started. Check logs:
docker compose logs zendriver
Try restarting the container:
docker compose restart zendriver
Check Chrome is installed in the container: docker compose exec zendriver which google-chrome
If missing, the base image may be incorrect. Verify you’re using: FROM ghcr.io/stephanlensky/swayvnc-chrome:latest
Example project structure
zendriver-docker/
├── docker-compose.yml
├── Dockerfile
├── pyproject.toml
├── uv.lock
├── app.py # Your Zendriver script
├── scripts/
│ └── test.sh # Test runner
├── output/ # Volume for screenshots/downloads
├── logs/ # Volume for logs
└── secrets/ # VNC credentials (git-ignored)
└── vnc_password.txt
Further resources
zendriver-docker Official Docker project template
swayvnc-chrome Base Docker image with Wayland and Chrome
Next steps
Anti-detection Configure anti-detection for production
Cloudflare bypass Handle Cloudflare challenges in Docker