← all articles

How to fix Failed to launch chromium in a Docker agent image

The build goes green. The image pushes to the registry. Then the container starts, the agent reaches its first chromium.launch(), and it dies with Executable doesn't exist at .... Nothing in a normal Docker build launches a browser, so nothing in CI ever tripped. You find out at container start, on the server, usually when a scheduled run was meant to happen.

This is the question behind microsoft/playwright issue 4033, and the short answer is that pip install playwright or npm install playwright installs the library, not the browser. The browser is a separate download. It lands in a per-user cache directory, and that directory is easy to lose between the build and the running container.

This is for operators running Playwright browser agents in Docker, Python or Node. By the end you’ll have a Dockerfile that puts chromium in a fixed path, runs as a non-root user, and fails the build instead of the deploy if the browser can’t start. Examples are Python; for Node use npx playwright install. I’ve only run this on Debian based images on x86-64, so treat arm64, firefox and webkit as untested by me.

what you need

  • Docker Engine or Docker Desktop with BuildKit, which current releases enable by default. Engine on Linux is free. Docker Desktop has paid tiers for larger companies, so check Docker’s pricing page for the terms as of September 2026.
  • a Playwright project with the version pinned exactly in requirements.txt or package-lock.json.
  • the Dockerfile of the failing agent image, and a shell where you can build and run it.
  • some spare disk. The browser and its apt packages make the image noticeably bigger than bare python-slim, so compare docker images before and after.
  • no paid accounts. Registry storage and CI minutes are the only real costs.

step by step

1. Reproduce the error and read the path

Run the image the way your deploy does.

docker run --rm my-agent:latest

Expected output, roughly (your revision number will differ):

playwright._impl._errors.Error: BrowserType.launch: Executable doesn't exist at /home/agent/.cache/ms-playwright/chromium_headless_shell-1148/chrome-linux/headless_shell
Looks like Playwright was just installed or updated.
Please run the following command to download new browsers:

    playwright install

The path tells you two things: which home directory Playwright searched, and which browser revision the installed library expects. Keep both for step 2.

If it breaks: a message like “Host system is missing dependencies to run browsers” means the browser exists but the OS libraries don’t. Step 4 covers that.

2. Work out which cause you have

docker run --rm --entrypoint sh my-agent:latest -c \
  'id -un; echo "HOME=$HOME"; ls -d /root/.cache/ms-playwright/* "$HOME"/.cache/ms-playwright/* /ms-playwright/* 2>&1'
docker run --rm --entrypoint pip my-agent:latest show playwright

Expected output: a user name, a home path, then either directory listings or “No such file” lines. Read them like this:

  • nothing found anywhere: playwright install never ran in the Dockerfile, or it ran in a build stage that never reached the final image.
  • browsers under /root/.cache while the container runs as another user: the classic case. The installer ran as root, the agent runs as agent, and Playwright looks in the agent’s own home.
  • browsers found, but the revision differs from the one in step 1: the library and the browser came from different versions.

If it breaks: if --entrypoint sh fails, the image has no shell (distroless or scratch). Playwright is a poor fit there, so use a Debian or Ubuntu base for anything that launches chromium.

3. Pin the Playwright version in one place

The browser revision is tied to the library version, and playwright install fetches whatever the installed library expects. So the two only drift apart when they come from different places: a floating playwright>=1.40 in requirements plus a cached layer from last month, or a pip version that doesn’t match an official image tag. Pin it exactly:

playwright==1.xx.0

Use the version you have tested, then check with pip freeze | grep -i playwright. Expected output is one line with that exact version.

If it breaks: pip reports a resolver conflict with a wrapper such as pytest-playwright. Loosen the wrapper, not the pin.

4. Install chromium into a fixed path at build time

This is the actual fix.

FROM python:3.12-slim-bookworm

# fixed path, so it does not depend on whose HOME the process has
ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt \
 && playwright install --with-deps chromium \
 && chmod -R a+rX /ms-playwright \
 && rm -rf /var/lib/apt/lists/*

RUN useradd --create-home --shell /bin/bash agent
COPY --chown=agent:agent . .
USER agent

CMD ["python", "agent.py"]

What each part does:

  • PLAYWRIGHT_BROWSERS_PATH is documented on Playwright’s browsers page. Moving the cache out of the home directory removes the root versus agent split, and because ENV values persist into the running container (Dockerfile reference), the runtime process sees the same path.
  • --with-deps installs the OS libraries with apt and needs root, so it runs before USER. Naming chromium skips firefox and webkit. In recent releases headless launches use a separate headless shell build and playwright install chromium fetches it too, which is why your error path may say headless_shell.
  • pip and playwright share one RUN so their cache layers expire together.

You can start from an official image instead. Playwright’s Docker docs publish tags like v1.xx.0-noble, and the tag must equal your library version exactly. I still prefer my own base, because I can see what is in it and choose the Python version, though you could argue the official image’s system dependencies are better maintained by the people who wrote the library. If you’re eyeing newer Ubuntu bases, I covered whether Playwright runs on Ubuntu 26.04 LTS yet. Which chromium flavour you run is a separate decision, compared in Chrome for Testing vs open-source Chromium for a Playwright agent fleet.

Expected output: apt installs, then chromium download lines, then a finished build.

If it breaks: a warning that your OS is not officially supported means the base image isn’t one Playwright targets. Switch to a Debian or Ubuntu tag. I stay off alpine for this reason.

5. Confirm the non-root user can see the browser

docker build -t my-agent:test .
docker run --rm --entrypoint sh my-agent:test -c 'id -un; ls /ms-playwright'

Expected output: agent, then a listing with chromium directories.

If it breaks: “No such file” usually means ENV sits after the install RUN, so the download went to the default cache. “Permission denied” means the chmod line is missing.

6. Move the failure to build time with a smoke launch

Add smoke_launch.py to the project:

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    print("chromium", browser.version)
    browser.close()

Then add this line to the Dockerfile after USER agent and before CMD, so it runs as the same user and HOME as production:

RUN python smoke_launch.py

Expected output in the build log: chromium followed by a version string. A missing browser is now a red build in CI, not a dead container at 3am.

If it breaks: error while loading shared libraries or “Host system is missing dependencies” means the apt step didn’t complete. Check that --with-deps is in the install command.

7. Run it with the flags chromium wants

docker run --rm --init --ipc=host my-agent:test

Playwright’s Docker docs recommend --ipc=host for chromium, because without it chromium can run out of memory and crash, and --init to avoid zombie processes. Docker’s default /dev/shm is 64 MB, which is small for a browser.

Expected output: the agent finishes its first browser task.

If it breaks: “Page crashed” or “Target closed” partway through is a shared memory problem, not a missing binary. You got past the launch, which is the point. Try --shm-size with a larger value.

8. Check what your orchestrator puts over the image

Compose volumes, Kubernetes persistent volumes and deploy scripts can hide or replace paths. Check inside the running container:

docker compose exec agent ls /ms-playwright
kubectl exec deploy/agent -- ls /ms-playwright

Using /ms-playwright instead of the home directory helps here, since home mounts no longer hide the browser. A volume mounted at /ms-playwright, or a deploy that pulls a stale latest tag, still can.

Expected output: the chromium directories. If it breaks: deploy by image digest or a unique tag rather than latest, and confirm the running pod’s image ID matches the one you built.

common pitfalls

  • Running playwright install in the entrypoint. It makes the error go away and I still say don’t. It downloads on every container start, needs outbound network and a writable disk, races when replicas boot together, and turns a build problem into a slow-start problem.
  • Installing as root, running as another user, and leaving the cache in its default location. Step 2 shows how to spot it.
  • Multi-stage builds that copy /ms-playwright into the final stage but not the apt libraries. The browser is there and launch still fails on shared libraries. Run the smoke launch in the final stage.
  • Leaving PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 set from a base image or CI config, which can make installs skip the download. Run env | grep PLAYWRIGHT during the build.
  • Trusting a green build. Green only means the image assembled. The browser agent production checklist has the runtime checks I’d add next.

scaling this

  • 10x: ten containers, one image, one Dockerfile. Nothing above changes, and the smoke launch in CI is the only guard you need.
  • 100x: pull time and image size start to show. Keep the browser layer early and stable so only the agent code layer changes per deploy, and pre-pull images onto nodes. Kubernetes usually won’t give you --ipc=host, so mount an emptyDir with medium: Memory at /dev/shm. Upgrade Playwright by rebuilding once and rolling every replica together, and log the chromium version at startup so traces and replays show which build ran.
  • 1000x: build once, promote the same digest through environments, and put a pull-through cache or regional mirror in front of the registry so nodes don’t all hit one place. At this size I’d consider pulling chromium out of the agent image into a small pool of browser containers the agents connect to over Playwright’s remote connection. I haven’t run that setup at this size myself, so weigh the extra network hop and lifecycle management before copying it. Also expect launch failures to stop being your main problem. Site-side blocking takes over, and why AI browser agents get blocked and how IP reputation is earned and lost explain that side, for agents working on your own properties or with consent and within each site’s terms.

where to go next

Every tutorial on the site is in the blog index.

Written by Xavier Fok

disclosure: this article may contain affiliate links. if you buy through them we may earn a commission at no extra cost to you. verdicts are independent of payouts. last reviewed by Xavier Fok on 2026-09-20.

free download
Why did my agent get blocked? A triage checklist

The checks we run, in order, when a browser or phone agent starts failing: network, fingerprint, behaviour, account. Leave your email and we will also tell you when we publish a new field note, a few times a month at most.

from the team behind this site
A Singapore mobile IP for browser agents

Singapore Mobile Proxy runs real mobile IPs on SingTel, StarHub and M1, with sticky sessions so one task keeps one IP. Singapore only: a fit for SEA or location-agnostic work, the wrong tool if you need a US IP.

see plans →
from the team behind this site
A real Android phone for phone-use agents

cloudf.one hosts real Android phones in Singapore on dedicated hardware, each with a persistent Singapore mobile IP. For agents that need an actual device and a stable carrier identity.

get a phone →
read on
More from The Agent Ops Report

Blocks, sessions, retries, traces, cost per task and phone-use agents. Browse all articles →