level 08 / playwright-in-docker

Playwright in Docker

Run Playwright tests reliably in Docker containers using the official Microsoft image.

The Official Playwright Docker Image

Microsoft publishes official images at mcr.microsoft.com/playwright:

# Pull specific version (always pin in CI)
docker pull mcr.microsoft.com/playwright:v1.45.0-jammy

# Available variants
mcr.microsoft.com/playwright:v1.45.0-jammy           # Ubuntu 22.04 (recommended)
mcr.microsoft.com/playwright:v1.45.0-focal           # Ubuntu 20.04
mcr.microsoft.com/playwright:v1.45.0-jammy-arm64     # Apple Silicon / ARM CI

Always pin the version taglatest will silently update and break reproducibility.

Dockerfile for Playwright Project

FROM mcr.microsoft.com/playwright:v1.45.0-jammy

WORKDIR /app

COPY package*.json ./
RUN npm ci --omit=dev

COPY playwright.config.ts .
COPY e2e/ ./e2e/
COPY src/ ./src/

ENV CI=true

CMD ["npx", "playwright", "test"]

Running Tests in Docker

# Basic run
docker run --rm \
  mcr.microsoft.com/playwright:v1.45.0-jammy \
  bash -c "cd /app && npx playwright test"

# With test project mounted (live code, no rebuild)
docker run --rm \
  -v $(pwd):/app \
  -w /app \
  -e BASE_URL=https://staging.example.com \
  mcr.microsoft.com/playwright:v1.45.0-jammy \
  npx playwright test

# Collect report
docker run --rm \
  -v $(pwd):/app \
  -w /app \
  -v $(pwd)/playwright-report:/app/playwright-report \
  mcr.microsoft.com/playwright:v1.45.0-jammy \
  npx playwright test --reporter=html

Headed Mode in Docker (with Xvfb)

The official image includes Xvfb for headed testing:

# Run with virtual display
docker run --rm \
  -e DISPLAY=:99 \
  mcr.microsoft.com/playwright:v1.45.0-jammy \
  bash -c "Xvfb :99 -screen 0 1280x1024x24 & npx playwright test --headed"

Generally unnecessary — use headless in CI. Headed Docker is for debugging specific rendering issues.

Security: Non-Root User

FROM mcr.microsoft.com/playwright:v1.45.0-jammy

WORKDIR /app

# Create non-root user
RUN groupadd -r pwuser && useradd -r -g pwuser -G audio,video pwuser \
  && mkdir -p /home/pwuser/Downloads \
  && chown -R pwuser:pwuser /home/pwuser \
  && chown -R pwuser:pwuser /app

COPY --chown=pwuser:pwuser package*.json ./
RUN npm ci

COPY --chown=pwuser:pwuser . .

USER pwuser

CMD ["npx", "playwright", "test"]

The official image already runs as a non-root user by default — check with docker run --rm mcr.microsoft.com/playwright:v1.45.0-jammy whoami.

Resource Limits

# Limit CPU and memory — prevent runaway containers in shared CI
docker run --rm \
  --cpus="2.0" \
  --memory="4g" \
  --memory-swap="4g" \
  my-pw-tests

Playwright + Chromium: budget ~500MB per worker. 4 workers = ~2GB.

Playwright Config Inside Docker

// playwright.config.ts — CI / Docker aware
export default defineConfig({
  workers: process.env.CI ? 2 : undefined,
  retries: process.env.CI ? 1 : 0,
  use: {
    headless: true,             // always headless in container
    launchOptions: {
      args: [
        '--no-sandbox',         // required in some Docker environments
        '--disable-setuid-sandbox',
        '--disable-dev-shm-usage',  // use /tmp instead of /dev/shm (small in Docker)
      ],
    },
  },
});

Note: The official Playwright image configures these flags automatically. Only add manually if using a custom base image.

CI Integration Example (GitHub Actions)

- name: Run Playwright in Docker
  run: |
    docker run --rm \
      -v ${{ github.workspace }}:/app \
      -w /app \
      -e BASE_URL=${{ vars.STAGING_URL }} \
      -e CI=true \
      mcr.microsoft.com/playwright:v1.45.0-jammy \
      npx playwright test --reporter=blob

  # Then upload the blob artifact for report merging