DevOps August 15, 2026 11 min read

Playwright Docker Tutorial: Run Tests in Containers (2026)

Running Playwright tests locally works — until it doesn't. Different OS versions, missing browser dependencies, and inconsistent environments break test suites across teams. Docker solves all of this. This guide covers everything from a basic Dockerfile to production-grade docker-compose and CI/CD pipeline configurations.

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 up and 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:

Available image tags
# 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.

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

  1. FROM mcr.microsoft.com/playwright:v1.50.0-noble — starts from the official image with all browsers and system dependencies pre-installed. No need for npx playwright install inside the container
  2. WORKDIR /app — creates and sets the working directory. All subsequent commands run from here
  3. COPY 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 cached npm ci layer and skips reinstallation
  4. RUN npm ci — installs exact versions from the lockfile. Faster than npm install and guarantees reproducible builds
  5. COPY . . — copies the rest of your source code, test files, and configuration
  6. CMD ["npx", "playwright", "test"] — sets the default command. You can override this at runtime with docker run arguments

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

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

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

Terminal
# 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.

docker-compose.yml
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_on with service_healthy — ensures the app is fully started and responding before Playwright runs. Without the healthcheck, tests might start before the server is ready
  • BASE_URL=http://app:3000 — Docker Compose creates an internal DNS entry for each service name. The test container reaches the app via the service name app, not localhost
  • ipc: 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

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

Dockerfile (Chromium only)
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:

Dockerfile (multi-stage)
# 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:

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

.github/workflows/playwright-docker.yml
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:

.github/workflows/playwright-docker-sharded.yml
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:

.gitlab-ci.yml
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:

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:

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

Dockerfile.debug
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
Terminal
# 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:

Terminal
# 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'.

Terminal
# 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.

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

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

Dockerfile (with custom fonts)
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 - Playwright and Claude AI course instructor

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.

Udemy Instructor Published course author
Playwright + AI Expert Specialized in AI-powered QA
Production Experience Enterprise-grade frameworks
Connect on LinkedIn