Playwright has become essential for E2E testing and web scraping. But deploying it in Docker production is often a struggle: Chromium won't start, permission errors, memory crashes...
This article gives you the keys to a robust configuration, tested in production.
Table of Contents
The problem: why it doesn't work "out of the box"
Chromium is not a simple library. It's a full browser with heavy system dependencies:
- Graphics libraries (libX11, libXcomposite, libXdamage...)
- System fonts
- Shared memory (/dev/shm)
- Linux sandboxing (namespaces, seccomp)
Locally, your OS provides all of this. In a minimalist container, nothing is present.
Building the Docker image
Choosing the base image
Avoid Alpine for Playwright. The musl vs glibc dependencies cause incompatibilities. Prefer Debian or Ubuntu.
# Recommended: official Playwright image
FROM mcr.microsoft.com/playwright:v1.40.0-jammy
# Alternative: Debian slim + manual installation
FROM python:3.11-slim-bookworm
Installing dependencies
With the official Playwright image, everything is included. Otherwise:
FROM python:3.11-slim-bookworm
# System dependencies for Chromium
RUN apt-get update && apt-get install -y --no-install-recommends \
libnss3 \
libnspr4 \
libatk1.0-0 \
libatk-bridge2.0-0 \
libcups2 \
libdrm2 \
libxkbcommon0 \
libxcomposite1 \
libxdamage1 \
libxfixes3 \
libxrandr2 \
libgbm1 \
libasound2 \
libpango-1.0-0 \
libcairo2 \
fonts-liberation \
&& rm -rf /var/lib/apt/lists/*
# Install Playwright
RUN pip install playwright && playwright install chromium
Non-root user configuration
Never run Chromium as root. It's a major security risk and disables sandboxing.
# Create a dedicated user
RUN useradd -m -u 1000 pwuser
# Working directory with proper permissions
WORKDIR /app
RUN chown -R pwuser:pwuser /app
# Install Playwright AS the user
USER pwuser
RUN playwright install chromium
# Cache will be in /home/pwuser/.cache/ms-playwright
Classic mistake: install as root, run as user
Bad
# Chromium installed as root
RUN pip install playwright
RUN playwright install chromium
USER pwuser
# Error: Chromium not found
# (installed in /root/.cache)
Good
# Chromium installed by user
RUN pip install playwright
USER pwuser
RUN playwright install chromium
# OK: in /home/pwuser/.cache
Playwright configuration for Docker
Essential Chromium arguments
from playwright.sync_api import sync_playwright
def get_browser():
playwright = sync_playwright().start()
browser = playwright.chromium.launch(
headless=True,
args=[
'--disable-dev-shm-usage', # Avoid memory crashes
'--no-sandbox', # Only if non-root user impossible
'--disable-setuid-sandbox',
'--disable-gpu', # No GPU in Docker
'--disable-software-rasterizer',
]
)
return browser
The /dev/shm trap
By default, Docker limits /dev/shm to 64MB. Chromium uses it for rendering and crashes if it's insufficient.
Solution 1: Increase the size (recommended)
# docker-compose.yml
services:
app:
shm_size: '2gb'
Solution 2: Disable shm usage
args=['--disable-dev-shm-usage']
Solution 1 is preferred for performance.
Resource management
Memory limits
Chromium is memory-hungry. Plan for at least:
- 512MB for a simple page
- 1-2GB for intensive scraping
- 4GB+ for multiple parallel tabs
# docker-compose.yml
services:
app:
deploy:
resources:
limits:
memory: 2G
reservations:
memory: 512M
Avoiding zombie processes
Chromium spawns subprocesses. Without an init system, they become zombies.
# docker-compose.yml
services:
app:
init: true # Adds tini as PID 1
Or in the Dockerfile:
RUN apt-get install -y tini
ENTRYPOINT ["/usr/bin/tini", "--"]
Complete Dockerfile and docker-compose
Dockerfile
FROM python:3.11-slim-bookworm
# System dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 \
libcups2 libdrm2 libxkbcommon0 libxcomposite1 \
libxdamage1 libxfixes3 libxrandr2 libgbm1 \
libasound2 libpango-1.0-0 libcairo2 \
fonts-liberation tini \
&& rm -rf /var/lib/apt/lists/*
# Non-root user
RUN useradd -m -u 1000 pwuser
WORKDIR /app
# Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Install Chromium as pwuser
USER pwuser
RUN playwright install chromium
# Application
COPY --chown=pwuser:pwuser . .
ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["python", "app.py"]
docker-compose.yml
version: '3.8'
services:
playwright-app:
build: .
shm_size: '2gb'
init: true
deploy:
resources:
limits:
memory: 2G
cpus: '2'
environment:
- PLAYWRIGHT_BROWSERS_PATH=/home/pwuser/.cache/ms-playwright
healthcheck:
test: ["CMD", "python", "-c", "import playwright; print('ok')"]
interval: 30s
timeout: 10s
retries: 3
Debugging
"Browser closed unexpectedly"
Common causes:
- Insufficient memory → Increase limits
- /dev/shm too small →
shm_size: '2gb' - Missing dependencies → Check libs
"Failed to launch browser"
# Check that Chromium is installed
docker exec -it container playwright install --dry-run
# Test manual launch
docker exec -it container python -c "
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
print('OK')
browser.close()
"
Detailed logs
DEBUG=pw:* python app.py
Production checklist
Before deploying
- Image based on Debian/Ubuntu (not Alpine)
- Non-root user
- Chromium installed by final user
shm_size: '2gb'minimuminit: trueto avoid zombies- Memory limits defined (2GB+ recommended)
--disable-dev-shm-usageif shm_size impossible- Healthcheck configured
- Fonts installed if text rendering needed
Conclusion
Playwright errors in Docker almost always come from:
- Permissions: Chromium installed as root, run as user
- Memory: /dev/shm too small or Docker limits too low
- Dependencies: missing system libs
With this article's configuration, you have a solid foundation for production.
Resources:
Have questions or feedback? Contact me or find me on LinkedIn.