Skip to main
Overhead view of a cargo ship in water
loaded with multi-colored shipping containers.

Running Playwright inside Docker containers

Learn how to run Playwright in headed mode to interact with the browser’s user interface from outside Docker containers.

Playwright is a test runner that uses real browsers to test web applications (an alternative to tools like Selenium). By default, Playwright runs these browsers in headless mode, which means the pages are loaded and tested without opening the browser window. This is great when running entire test suites locally, and in CI where having a bunch of rapidly opening windows would be disconcerting. However, when it comes to writing or debugging individual tests, it is convenient to open the browser in headed mode to actually see the page being tested.

Playwright includes the Playwright Inspector to conveniently launch the browser in headed mode and a separate window to control test execution. The inspector is launched by setting PWDEBUG=1 before calling playwright, and is compatible with all browsers supported by Playwright (Safari, Chrome, and Firefox) in all major operating systems. Playwright also includes a playwright open <url> subcommand to quickly launch the inspector on any URL. Examples in this post use playwright open <url>, but PWDEBUG=1 is equivalent.

Playwright also provides a convenient Docker image, which includes all three browsers pre-installed and configured so you can skip the dependency installation steps. But what happens if you try to run the Playwright Inspector inside a Docker container – which normally doesn’t have a graphical user interface?

# We expect a browser window to open and load
# Update the image tag to match your desired version of Playwright
docker run --rm npx -y playwright open

║ Looks like you launched a headed browser without having a XServer running.                     ║
║ Set either 'headless: true' or use 'xvfb-run <your-playwright-app>' before running Playwright. ║
║                                                                                                ║
║ <3 Playwright Team                                                                             ║

No browser window is launched! Instead we get an error message about not “having a XServer running.” The definition and functionality of XServer are beyond the scope of this article, but without it we can’t interact with applications that require a user interface (like the browser). Here’s a more detailed explanation if you want to learn more.

Searching for this error on the web will return results explaining how to install and start XServer. That advice applies to non-containerized, Linux-based systems. If your host system is macOS or Windows you actually don’t want to do that. Instead we want the container to use the host XServer to launch Playwright inside the container, which requires two modifications to our docker command:

  1. Set the DISPLAY environment variable inside the container using the -e option
  2. Mount the XServer Unix socket inside the container using the -v option
docker run --rm \
-e DISPLAY=<host display> \
-v /tmp/.X11-unix:/tmp/.X11-unix \ npx -y playwright open

The value of <host display> will depend on your host operating system, and you will need to ensure /tmp/.X11-unix is available for mounting. The following sections explain how to do this for Windows and macOS.

You might find it surprising (I certainly did) that Microsoft Windows has a native XServer even though it’s not a GNU/Linux system. It’s called WSLg, and it’s included as part of the Windows Subsystem for Linux (WSL). You most likely already have WSL and WSLg installed if you are running Docker Desktop in recent builds of Windows 10 and 11.

Let’s start by verifying that WSL and WSLg are installed and running. First, launch “WSL” from your Start Menu. A Linux terminal window should open (most likely a recent version of Ubuntu). In that window, verify that the directory /mnt/wslg/ exists and contains these files inside the Linux filesystem:

ls -a -w 1 /mnt/wslg


If you don’t see “WSL” in your Start Menu, or the ls command above fails with No such file or directory, then your system is missing WSL entirely or is running an old version. Visit the Microsoft Store to download an up-to-date version.

Once you are all set up, we can set DISPLAY=:0 as explained in the official guide:

docker run --rm \
-e DISPLAY=:0 \
-v /tmp/.X11-unix:/tmp/.X11-unix \ npx -y playwright open

If all goes well, that should open two windows: a browser window with Google loaded, and a Playwright Inspector window. Closing both will also stop the container.

Apple’s operating system doesn’t include a built-in XServer, but we can use XQuartz to provide one:

  1. Install XQuartz: brew install --cask xquartz
  2. Open XQuartz, go to Preferences -> Security, and check “Allow connections from network clients”
  3. Restart your computer (restarting XQuartz might not be enough)
  4. Start XQuartz with xhost +localhost
  5. Open Docker Desktop and edit settings to give access to /tmp/.X11-unix in Preferences -> Resources -> File sharing

Once XQuartz is running with the right permissions, you can populate the environment variable and socket:

docker run --rm \
-e DISPLAY=host.docker.internal:0 \
-v /tmp/.X11-unix:/tmp/.X11-unix \ npx -y playwright open

If all goes well, that should open two windows: a browser window with Google loaded, and a Playwright Inspector window. Closing both will also stop the container.

To avoid having to specify the flags every time, you can use a docker-compose.yml file to set the environment variable and mount the socket.

# docker-compose.yml
version: '3'

      - DISPLAY=... # Replace this line with the appropriate value
      - /tmp/.X11-unix:/tmp/.X11-unix

Remember: environment needs DISPLAY=:0 in Windows and DISPLAY=host.docker.internal:0 in macOS. After editing and saving the file, run docker-compose to open the browser (or run any Playwright command) along with the Playwright Inspector:

docker-compose run web npx -y playwright open

Enjoy using and inspecting the browser from inside containers!

Recent Articles

  1. A stand of smartphones
    Article post type

    When to Choose a Native Mobile App

    Part 2 – Responsive Web App vs Native Mobile App vs Progressive Web App

    If you have an idea for a digital product, you may be wondering if you should build a responsive web app, a native mobile app, or a progressive web app. Is one option inherently better? What are the pros and cons? This is part 2 of a three-part series unpacking…

    see all Article posts
  2. spider web with dew drops
    Article post type

    When to Choose a Responsive Web App

    Part 1 – Responsive Web App vs Native Mobile App vs Progressive Web App

    The decision of what platform to use to build your app is quite important – affecting project scope, timeline, and budget. But understanding the differences between a responsive web app, a native mobile app, and a progressive web app – and deciding which one is right for your project …

    see all Article posts
  3. The top front of a bright yellow shipping container with the door open and a blue sky behind it
    Article post type

    Can We Query the Root Container?

    The complexities of containment, overflow, and ‘propagation’

    I spoke about Container Queries at both Smashing Conference (San Francisco) and CSS Day (Amsterdam) – where I recommended setting up a root container to replace most media queries. Since then, Temani Afif pointed out a few issues with that approach, and sent me down a rabbit hole of overlapping…

    see all Article posts