Skip to main content
/tayyab/portfolio — zsh
tayyab
TA
// dispatch.read --classified=false --access-level: public

Playwright in Docker: The Setup That Actually Works on M1/M2/M3 Macs (2026)

April 1, 2026 EST. READ: 11 MIN #DevOps & Engineering

Run Playwright in Docker on a Linux x86 machine and everything works. Run it on an M1, M2, or M3 Mac — increasingly common in 2026 — and you hit the rough edges. The official image (mcr.microsoft.com/playwright) ships ARM64 variants now, but they have subtle differences from the AMD64 version: different fonts, different default browser builds, occasional segfaults under emulation.

I've spent enough time fighting this on three different client setups that I have a known-good config. Sharing it here so you don't have to find every trap individually.

Table of Contents

Why M1/M2/M3 + Docker Is Harder Than Linux

Three reasons:

  1. Architecture mismatch. Apple Silicon is ARM64. Most CI runners are x86_64. Same Playwright code; different binaries underneath.
  2. Browser builds differ. Chromium ARM64 and Chromium AMD64 are compiled separately. Most behavior matches; occasional rendering differences exist.
  3. Font availability. Linux containers don't include the Mac system fonts. Your tests render with Liberation Sans where production renders with SF Pro. Visual diffs explode.

The first two are mostly solved at this point. The third one still bites people.

The Right Image to Use

Use Microsoft's official Playwright image with explicit version pinning. Do not use :latest:

FROM mcr.microsoft.com/playwright:v1.59.0-noble

Two things matter:

  • Pin the Playwright version. The image must match your @playwright/test version exactly. Mismatches cause cryptic browser-launch errors.
  • Use the multi-arch tag. The MS images are multi-architecture by default — pulling on an M1 gets you ARM64, pulling on x86 CI gets you AMD64. No special flag needed.

Avoid the older Ubuntu Focal/Jammy variants for new projects. Noble (24.04) has the most current Chromium build and the best font defaults.

A Working Dockerfile for Local Dev

FROM mcr.microsoft.com/playwright:v1.59.0-noble

WORKDIR /app

# Install dependencies first for caching
COPY package.json package-lock.json ./
RUN npm ci

# Add common Linux fonts so screenshots match production-like rendering
RUN apt-get update && apt-get install -y \
    fonts-liberation \
    fonts-noto-color-emoji \
    fonts-noto-cjk \
    fonts-roboto \
    && rm -rf /var/lib/apt/lists/*

# Copy source last for faster rebuilds
COPY . .

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

Build and run:

docker build -t qa-tests .
docker run --rm -v "$(pwd):/app" qa-tests

The volume mount lets you edit tests on your Mac and run them in the container without rebuilding.

Docker Compose for Local + CI Parity

The biggest win of Docker for QA is reproducibility. Local matches CI. Compose makes that ergonomic:

# docker-compose.yml
services:
  app:
    build: .
    ports:
      - '3000:3000'
    environment:
      DATABASE_URL: postgres://user:pass@db:5432/test

  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: test

  tests:
    image: mcr.microsoft.com/playwright:v1.59.0-noble
    depends_on:
      - app
      - db
    volumes:
      - .:/app
    working_dir: /app
    environment:
      BASE_URL: http://app:3000
      DATABASE_URL: postgres://user:pass@db:5432/test
    command: npx playwright test

Run:

docker compose up --abort-on-container-exit --exit-code-from tests

Same command on Mac, Linux dev box, and CI. If a test fails locally, it fails in CI for the same reason.

Font Consistency Between Mac and Linux CI

Visual regression baselines die the moment fonts differ. The fix has two halves.

Half 1: Always generate baselines from the same environment

Pick one: "only CI generates baselines" or "everyone uses Docker locally to generate baselines." Don't mix.

The first is simpler:

# .github/workflows/update-baselines.yml
on:
  workflow_dispatch:

jobs:
  update:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npx playwright test --update-snapshots
      - uses: peter-evans/create-pull-request@v6
        with:
          branch: update-snapshots
          title: Update visual regression baselines

Devs trigger the workflow, review the resulting PR, merge.

Half 2: Install the same fonts everywhere

If you must generate locally on Mac and have it match CI, install the same font set in both places. The Dockerfile above includes Liberation, Noto, and Roboto — install the same fonts on Mac (via Homebrew) and your renderings stay closer to identical.

Honest take: this is a losing battle. Use option 1 instead.

Performance: When to Use Docker vs Run Native

Docker on Apple Silicon adds 5–15% CPU overhead via the lightweight VM Docker Desktop runs. For a typical Playwright suite that's 30–90 seconds added on a 10-minute run.

Use Docker for:

  • Generating baselines (consistency with CI matters more than speed).
  • Reproducing CI-only failures.
  • Running tests against a containerized backend.

Run native for:

  • Active test development (you're iterating on code, want feedback in seconds).
  • The Inspector / UI mode (better visual experience without a container layer).
  • Any time the speed delta matters more than the consistency delta.

Most teams I work with end up running both — native day-to-day, Docker for baseline updates and CI repro.

Common Errors and Their Fixes

Error: Executable doesn't exist at ...

Browser binary doesn't match your Playwright version. Run npx playwright install --with-deps inside the container, or rebuild with the correct image tag.

Error: Browserview crashed: SIGTRAP

Usually an out-of-memory error in low-memory Docker setups. Increase Docker Desktop's memory allocation to at least 4GB. Set --ipc=host for Chrome stability:

docker run --ipc=host --rm -v "$(pwd):/app" qa-tests

Error: tests pass on Mac, fail in Docker

Different rendering or different timing. Most often font-related (your test has a visual assertion that depended on macOS fonts) or animation-related (Linux container is slower, animations didn't finish). Use the disabled-animations pattern from my race conditions post.

Error: net::ERR_CONNECTION_REFUSED when test hits the app

Inside Docker, localhost means the container, not the host. If your app runs on the host, use host.docker.internal on Mac/Windows or run the app in another container and reference it by service name.

Error: extreme slowness on M1 with x86 image

You're running an AMD64 image under emulation. Pull the multi-arch tag (the official MS image handles this) or specify the platform explicitly: docker pull --platform linux/arm64 mcr.microsoft.com/playwright:v1.59.0-noble.

FAQs

Should I commit the Dockerfile to my repo?

Yes. It's the documentation for your test environment. Without it, every dev sets it up differently.

What about Podman or Colima instead of Docker Desktop?

Both work. Colima is lighter on Apple Silicon. Same Dockerfile applies; runtime differs.

How do I share the same image across multiple repos?

Build once, push to your container registry, FROM it in each repo's Dockerfile. Standard multi-repo container pattern.

What about GitHub Actions native vs running in a container?

Actions runs Linux x86 by default. If your container is also Linux x86, you don't need to wrap. The container only helps when you want to control the toolchain version explicitly.

How do I debug inside the container?

docker compose run tests bash drops you into a shell. Run tests interactively from there.

Can I run UI mode (--ui) inside Docker?

Technically yes with X11 forwarding, but it's painful. Run UI mode native; reserve Docker for headless test runs.

What about running on Apple Silicon CI runners?

GitHub now offers ARM64 macOS runners. If your team needs to test specifically on Apple Silicon, those exist. Most teams stick to Linux x86 CI and treat Apple Silicon as a dev-only environment.

How do I keep the image size small?

The base Playwright image is about 1.5GB. Strip what you don't need with multi-stage builds. For small CI images, use mcr.microsoft.com/playwright:v1.59.0-noble-amd64 or build your own thin variant.

Should I use Alpine Linux for smaller images?

No. Playwright doesn't officially support Alpine; you'll hit obscure musl vs glibc issues. Stick with Noble (24.04) or Jammy (22.04).

What about Windows containers?

Different category. If you need to test specifically against Edge on Windows, use Windows runners directly, not Windows containers.

Wrap-Up

Playwright in Docker on Apple Silicon is a solved problem if you know the right knobs. Pin the image version, use the multi-arch tag, install consistent fonts, generate baselines from one environment only. The setup pays off the first time a CI failure reproduces locally with one command.

If your team is migrating to a Docker-based test environment and you want a sanity check on the config, that's part of framework engagements. Or book a free call.

Related reading:

Tayyab Akmal
// author

Tayyab Akmal

AI & QA Automation Engineer

6 years of catching critical bugs in fintech, e-commerce, and SaaS — then building the Playwright and Selenium automation that prevents them from shipping again.

// feedback_channel

FOUND THIS USEFUL?

Share your thoughts or let's discuss automation testing strategies.

→ Start Conversation
Available for hire