If you've ever heard "but it passes on my machine," you already know why containerized testing matters. Browser-based tests are notoriously environment-sensitive — a missing font library on Ubuntu, a different Chromium version on a colleague's Mac, or a CI runner with outdated dependencies can all produce false failures.
Docker eliminates these variables by packaging your tests, browsers, and all system dependencies into a single reproducible image. Every developer, every CI runner, and every environment runs the exact same setup. This guide walks you through the full Playwright + Docker workflow, from writing your first Dockerfile to deploying in GitHub Actions and GitLab CI.
1. Why Run Playwright in Docker?
Before diving into configuration files, let's be clear on why Docker is worth the setup cost for Playwright testing:
- Environment consistency — every team member and CI runner uses the same OS, browser binaries, system fonts, and library versions. No more "works on my machine" debugging sessions
- No browser install hassles — Playwright needs Chromium, Firefox, and WebKit plus dozens of system-level dependencies (libgbm, libatk, fonts). The official Docker image bundles all of them
- CI/CD simplicity — pull an image and run tests. No multi-step browser installation scripts, no caching browser binaries between runs, no dependency drift over time
- Team standardization — new developers run
docker compose upand immediately have a working test environment. No 30-minute onboarding docs for "setting up Playwright locally" - Isolation — tests run in a sandboxed container that doesn't interfere with the host machine. No leftover browser processes, no port conflicts, no polluted global state
The trade-off is a small overhead in build time and image size. But as you'll see, multi-stage builds and targeted browser installation keep images lean.
When to skip Docker: If you're a solo developer running tests only on your own machine and don't use CI/CD, Docker adds complexity without much benefit. Start with npx playwright install --with-deps and adopt Docker when you add CI or a second team member.
2. Official Playwright Docker Images
Microsoft maintains official Docker images specifically for Playwright at mcr.microsoft.com/playwright. These images are the foundation of any containerized Playwright setup.
Image Registry and Tags
The images are hosted on Microsoft Container Registry (MCR), not Docker Hub. Available tags follow a consistent versioning pattern:
# Versioned tag (recommended for production) mcr.microsoft.com/playwright:v1.50.0-noble # Latest tag (always points to newest release) mcr.microsoft.com/playwright:latest # Specific Ubuntu version mcr.microsoft.com/playwright:v1.50.0-jammy # Ubuntu 22.04 mcr.microsoft.com/playwright:v1.50.0-noble # Ubuntu 24.04
The tag format is v{playwright-version}-{ubuntu-codename}. In 2026, noble (Ubuntu 24.04 LTS) is the recommended base.
What's Included
Each official image ships with:
- All three browser engines — Chromium, Firefox, and WebKit, pre-installed and matched to the Playwright version
- System dependencies — all shared libraries (
libgbm,libatk,libnss3, etc.) required by the browsers - Fonts — standard web fonts for consistent rendering across Linux containers
- Node.js — the LTS version of Node.js, ready to use
Version pinning is critical: Always match the image tag to your @playwright/test npm version. If your package.json has "@playwright/test": "1.50.0", use mcr.microsoft.com/playwright:v1.50.0-noble. Mismatched versions cause browser launch failures.
3. Writing a Dockerfile for Playwright Tests
Here's a complete, production-ready Dockerfile with line-by-line explanations. This handles dependency installation, project setup, and test execution.
# Use the official Playwright image with all browsers pre-installed FROM mcr.microsoft.com/playwright:v1.50.0-noble # Set the working directory inside the container WORKDIR /app # Copy dependency files first (leverages Docker layer caching) COPY package.json package-lock.json ./ # Install project dependencies (ci = clean install, faster + deterministic) RUN npm ci # Copy the rest of the project files COPY . . # Default command: run all Playwright tests CMD ["npx", "playwright", "test"]
Let's break down why each line matters:
FROM mcr.microsoft.com/playwright:v1.50.0-noble— starts from the official image with all browsers and system dependencies pre-installed. No need fornpx playwright installinside the containerWORKDIR /app— creates and sets the working directory. All subsequent commands run from hereCOPY package.json package-lock.json ./— copies only dependency files first. This is a Docker caching optimization: if your package files haven't changed, Docker reuses the cachednpm cilayer and skips reinstallationRUN npm ci— installs exact versions from the lockfile. Faster thannpm installand guarantees reproducible buildsCOPY . .— copies the rest of your source code, test files, and configurationCMD ["npx", "playwright", "test"]— sets the default command. You can override this at runtime withdocker runarguments
Layer caching tip: By copying package.json before the rest of the project, Docker only re-runs npm ci when dependencies change. When you only modify test files, the build skips straight to the COPY . . step — saving 30–60 seconds per build.
4. Running Tests with Docker
With the Dockerfile in place, here are the essential commands for building and running your containerized tests.
Build the Image
# Build the image and tag it docker build -t playwright-tests . # Build with no cache (force fresh install) docker build --no-cache -t playwright-tests .
Run Tests
# Basic run (uses CMD from Dockerfile) docker run --rm playwright-tests # IMPORTANT: use --ipc=host to prevent browser crashes docker run --rm --ipc=host playwright-tests # Run specific test file docker run --rm --ipc=host playwright-tests npx playwright test login.spec.ts # Run with grep filter docker run --rm --ipc=host playwright-tests npx playwright test --grep "@smoke"
Mount Volumes for Results
By default, test results stay inside the container and disappear when it exits. Mount volumes to persist reports and traces on your host machine:
# Mount test results and report to host docker run --rm --ipc=host \ -v $(pwd)/test-results:/app/test-results \ -v $(pwd)/playwright-report:/app/playwright-report \ playwright-tests # Pass environment variables docker run --rm --ipc=host \ -e BASE_URL=https://staging.example.com \ -e CI=true \ playwright-tests # Use an env file for multiple variables docker run --rm --ipc=host \ --env-file .env.test \ playwright-tests
The --ipc=host flag is not optional. Docker limits /dev/shm (shared memory) to 64MB by default. Chromium uses shared memory heavily for rendering, and 64MB is not enough. Without --ipc=host or --shm-size=1gb, your browsers will crash with cryptic "Target closed" or "Browser has been closed" errors.
5. Docker Compose for Playwright
Docker Compose shines when you need to test against a real application. Instead of pointing Playwright at a remote URL, you spin up the app and test runner together as services.
version: "3.9" services: # Your web application app: build: context: . dockerfile: Dockerfile.app ports: - "3000:3000" healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/health"] interval: 5s timeout: 3s retries: 10 start_period: 10s networks: - test-network # Playwright test runner tests: build: context: . dockerfile: Dockerfile depends_on: app: condition: service_healthy environment: - BASE_URL=http://app:3000 - CI=true ipc: host volumes: - ./test-results:/app/test-results - ./playwright-report:/app/playwright-report networks: - test-network networks: test-network: driver: bridge
Key points about this configuration:
depends_onwithservice_healthy— ensures the app is fully started and responding before Playwright runs. Without the healthcheck, tests might start before the server is readyBASE_URL=http://app:3000— Docker Compose creates an internal DNS entry for each service name. The test container reaches the app via the service nameapp, notlocalhostipc: host— the compose equivalent of--ipc=host, preventing browser shared memory crashes- Volumes — test reports persist on the host machine after the containers stop
Running the Compose Stack
# Start app + run tests (build if needed) docker compose up --build --abort-on-container-exit # Run tests only (if app is already running) docker compose run --rm tests # Run a specific test file docker compose run --rm tests npx playwright test checkout.spec.ts # Tear down everything docker compose down --volumes
The --abort-on-container-exit flag is important: it stops all services when the test container finishes, so the app container doesn't hang around indefinitely.
6. Optimizing Docker Image Size
The official Playwright image with all three browsers weighs roughly 2.2GB. For CI pipelines where image pull time matters, that's worth optimizing. Here are three strategies to shrink it.
Strategy 1: Install Only the Browsers You Need
If you only test on Chromium (the most common case), skip Firefox and WebKit entirely:
FROM mcr.microsoft.com/playwright:v1.50.0-noble WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci # Install only Chromium (saves ~800MB) RUN npx playwright install --with-deps chromium COPY . . CMD ["npx", "playwright", "test", "--project=chromium"]
Strategy 2: Multi-Stage Builds
Use a multi-stage build to install dependencies in a build stage and copy only the essentials to the final image:
# Stage 1: Install dependencies FROM mcr.microsoft.com/playwright:v1.50.0-noble AS deps WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci --production=false # Stage 2: Test runner FROM mcr.microsoft.com/playwright:v1.50.0-noble WORKDIR /app # Copy node_modules from deps stage COPY --from=deps /app/node_modules ./node_modules # Copy project files COPY . . # Run as non-root user for security USER pwuser CMD ["npx", "playwright", "test"]
Strategy 3: .dockerignore
A proper .dockerignore file prevents unnecessary files from bloating the build context and the final image:
node_modules test-results playwright-report blob-report .git .github .vscode *.md .env .env.* coverage dist
With these three optimizations combined, you can reduce the image from 2.2GB to under 1.2GB, cutting CI pull time by nearly half.
7. Playwright Docker in GitHub Actions
Using Docker in GitHub Actions gives you the best of both worlds: the consistency of containers plus GitHub's native CI features like matrix sharding, artifact uploads, and PR status checks. For the full GitHub Actions deep-dive, see our Playwright GitHub Actions CI/CD guide.
name: Playwright Tests (Docker) on: push: branches: [main] pull_request: branches: [main] jobs: test: runs-on: ubuntu-latest container: image: mcr.microsoft.com/playwright:v1.50.0-noble options: --ipc=host steps: - uses: actions/checkout@v4 - name: Cache node_modules uses: actions/cache@v4 with: path: node_modules key: deps-${{ hashFiles('package-lock.json') }} - name: Install dependencies run: npm ci - name: Run Playwright tests run: npx playwright test env: CI: true - name: Upload test report uses: actions/upload-artifact@v4 if: always() with: name: playwright-report path: playwright-report/ retention-days: 14 - name: Upload traces on failure uses: actions/upload-artifact@v4 if: failure() with: name: playwright-traces path: test-results/ retention-days: 7
Sharded Docker Pipeline
For large test suites, combine Docker with GitHub Actions' matrix strategy to run tests in parallel across multiple containers:
jobs: test: runs-on: ubuntu-latest container: image: mcr.microsoft.com/playwright:v1.50.0-noble options: --ipc=host strategy: fail-fast: false matrix: shard: [1/4, 2/4, 3/4, 4/4] steps: - uses: actions/checkout@v4 - run: npm ci - name: Run tests (shard ${{ matrix.shard }}) run: npx playwright test --shard=${{ matrix.shard }} - uses: actions/upload-artifact@v4 if: always() with: name: report-${{ strategy.job-index }} path: playwright-report/
With 4 shards, a 20-minute test suite completes in under 5 minutes. The fail-fast: false setting ensures all shards finish even if one fails, giving you a complete picture of failures.
8. Playwright Docker in GitLab CI
GitLab CI has first-class Docker support through the image directive. Here's a working .gitlab-ci.yml configuration:
stages: - test playwright-tests: stage: test image: mcr.microsoft.com/playwright:v1.50.0-noble variables: CI: "true" before_script: - npm ci script: - npx playwright test artifacts: when: always paths: - playwright-report/ - test-results/ expire_in: 7 days retry: max: 1 when: - runner_system_failure # Parallel sharding variant playwright-sharded: stage: test image: mcr.microsoft.com/playwright:v1.50.0-noble parallel: 4 before_script: - npm ci script: - npx playwright test --shard=$CI_NODE_INDEX/$CI_NODE_TOTAL artifacts: when: always paths: - playwright-report/ expire_in: 7 days
GitLab's parallel keyword automatically sets $CI_NODE_INDEX and $CI_NODE_TOTAL, which map directly to Playwright's --shard flag. No matrix configuration needed.
GitLab shared runners: GitLab's shared runners use Docker-in-Docker. If you need --ipc=host, you may need to use a dedicated runner with privileged mode. Alternatively, increase shared memory with --shm-size=1gb in your runner config.
9. Debugging Tests Inside Docker
When tests fail only inside Docker, you need visibility into what's happening inside the container. Here are three approaches, from simplest to most powerful.
Approach 1: Trace Viewer
Playwright's built-in trace viewer is the easiest debugging tool. Enable it in your playwright.config.ts:
export default defineConfig({ use: { // Record trace on first retry of a failed test trace: 'on-first-retry', // Or always record traces in CI trace: process.env.CI ? 'on' : 'on-first-retry', }, });
Then extract trace files from the container:
# Run with trace extraction docker run --rm --ipc=host \ -v $(pwd)/test-results:/app/test-results \ playwright-tests # View the trace locally npx playwright show-trace test-results/my-test/trace.zip
Approach 2: Headed Mode with VNC
For interactive debugging, run browsers in headed mode inside the container and connect via VNC to see the actual browser window:
FROM mcr.microsoft.com/playwright:v1.50.0-noble # Install VNC server and window manager RUN apt-get update && apt-get install -y \ x11vnc \ xvfb \ fluxbox \ && rm -rf /var/lib/apt/lists/* WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci COPY . . # Expose VNC port EXPOSE 5900 # Start Xvfb + VNC + run tests headed CMD Xvfb :99 -screen 0 1920x1080x24 & \ export DISPLAY=:99 && \ fluxbox & \ x11vnc -display :99 -forever -nopw & \ npx playwright test --headed
# Build and run the debug container docker build -f Dockerfile.debug -t pw-debug . docker run --rm --ipc=host -p 5900:5900 pw-debug # Connect with any VNC client to localhost:5900
Approach 3: Interactive Shell
Drop into the container to manually inspect the environment:
# Start an interactive bash session docker run --rm -it --ipc=host playwright-tests /bin/bash # Inside the container, run tests manually npx playwright test --debug login.spec.ts # Check installed browsers npx playwright --version npx playwright install --dry-run
10. Common Docker + Playwright Issues
Here are the most frequent problems teams encounter when running Playwright in Docker, along with their solutions.
Browser Crashes (Shared Memory)
Symptom: browserType.launch: Browser closed unexpectedly or Target closed errors.
Fix: Add --ipc=host to your docker run command or --shm-size=1gb. In docker-compose, add ipc: host or shm_size: '1gb'.
# Option A: Share host IPC namespace docker run --ipc=host playwright-tests # Option B: Increase shared memory size docker run --shm-size=1gb playwright-tests
Out of Memory (OOM Killed)
Symptom: Container exits with code 137 or tests hang and timeout.
Fix: Increase Docker's memory limit. Each browser instance needs roughly 200–500MB. Running 5 parallel workers with 3 browser projects can require 4–6GB.
# Set memory limit to 4GB docker run --memory=4g --ipc=host playwright-tests # Reduce parallel workers in playwright.config.ts # workers: process.env.CI ? 2 : undefined
Permission Errors
Symptom: EACCES: permission denied when writing test results or screenshots.
Fix: The official image includes a pwuser user. If you run as pwuser, make sure mounted volumes are writable. Alternatively, set the user explicitly:
# Run as current user (match host UID/GID) docker run --rm --ipc=host \ --user "$(id -u):$(id -g)" \ -v $(pwd)/test-results:/app/test-results \ playwright-tests
Font Rendering Differences
Symptom: Visual regression tests show pixel differences between local and Docker runs, especially in text rendering.
Fix: Install the same fonts in Docker that your application uses. Add font installation to your Dockerfile:
FROM mcr.microsoft.com/playwright:v1.50.0-noble # Install system fonts for consistent rendering RUN apt-get update && apt-get install -y \ fonts-liberation \ fonts-noto-color-emoji \ fonts-noto-cjk \ && rm -rf /var/lib/apt/lists/* \ && fc-cache -fv
Pro tip: Always generate your visual regression baseline screenshots inside Docker, not on your local machine. This ensures the baseline and comparison environments are identical, eliminating false diff noise from font rendering and anti-aliasing differences.
11. Containerized Testing with Claude AI
Writing Dockerfiles, docker-compose configs, and CI/CD pipelines involves a lot of boilerplate. Claude AI can generate production-ready Docker configurations for Playwright in seconds.
In the Playwright + Claude AI & MCP Server course, you'll learn to:
- Generate Dockerfiles from prompts — describe your testing requirements and let Claude produce an optimized, multi-stage Dockerfile
- Auto-create docker-compose setups — Claude analyzes your project structure and generates service definitions with healthchecks, networks, and volume mounts
- Debug container issues with AI — paste error logs and get instant diagnosis and fixes for shared memory, permission, and browser crash issues
- Generate CI/CD pipelines — produce GitHub Actions and GitLab CI configs tailored to your Docker setup with caching, sharding, and artifact handling
FAQ
What is the official Playwright Docker image?
The official image is mcr.microsoft.com/playwright, hosted on Microsoft Container Registry. It ships with all three browser engines (Chromium, Firefox, WebKit), system dependencies, fonts, and Node.js. Use versioned tags like v1.50.0-noble and pin them to match your @playwright/test npm version.
Why do Playwright browsers crash inside Docker?
Browser crashes are almost always caused by insufficient shared memory. Docker defaults /dev/shm to 64MB, but Chromium requires more. Add --ipc=host or --shm-size=1gb to your docker run command. In docker-compose, add ipc: host to your service definition.
Can I use Docker Compose for Playwright E2E testing?
Yes, and it's the recommended approach for testing against a real app. Define your application and Playwright runner as separate services with depends_on and a healthcheck. The test container reaches the app via Docker's internal DNS using the service name instead of localhost.
How do I reduce Playwright Docker image size?
Three strategies: (1) Install only the browsers you need with npx playwright install chromium instead of all three, saving ~800MB; (2) Use multi-stage builds to separate dependency installation from the runtime image; (3) Add a .dockerignore file to exclude node_modules, test results, and other non-essential files. Combined, these reduce images from 2.2GB to under 1.2GB.
How do I debug Playwright tests failing only in Docker?
Three approaches: (1) Enable Playwright traces with trace: 'on' in your config and mount test-results to your host with -v; (2) Set up VNC inside the container to watch headed tests in real time; (3) Start an interactive shell with docker run -it and run tests manually with --debug. Start with traces — they solve 90% of Docker-specific failures.
Asim Noaman
Senior QA Automation Engineer & AI Testing Specialist
With years of hands-on experience building test automation frameworks for production applications, Asim specializes in combining traditional QA methodologies with cutting-edge AI tools. He has helped teams adopt Playwright and AI-driven testing workflows to ship faster with fewer bugs.