Back to articles

Playwright and Chromium in Docker Production: A Practical Guide

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
  1. The problem
  2. Building the Docker image
  3. Non-root user configuration
  4. Playwright configuration
  5. Resource management
  6. Complete Dockerfile and docker-compose
  7. Debugging
  8. Production checklist

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:

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:

# 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:

  1. Insufficient memory → Increase limits
  2. /dev/shm too small → shm_size: '2gb'
  3. 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' minimum
  • init: true to avoid zombies
  • Memory limits defined (2GB+ recommended)
  • --disable-dev-shm-usage if shm_size impossible
  • Healthcheck configured
  • Fonts installed if text rendering needed

Conclusion

Playwright errors in Docker almost always come from:

  1. Permissions: Chromium installed as root, run as user
  2. Memory: /dev/shm too small or Docker limits too low
  3. 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.

Thomas Bourimech

Thomas Bourimech

Freelance Developer & AI Integrator

thomasbourimech.com