在 Docker 里跑 Playwright:官方镜像与自建镜像的取舍
用例在本机跑得好好的,推到流水线上就变成浏览器起不来,这是把 Playwright 搬进容器时最常见的第一道坎。原因通常不在你的代码,而在于容器基础镜像里缺一堆图形与字体相关的系统库。摆在面前的选择只有两条:直接用官方发布的镜像,或者拿自己的基础镜像补依赖。这两条路仓库里都写了,边界也划得挺清楚,值得先看明白再动手。
一、先分清镜像里有什么、没有什么
docs/src/docker.md 开头就把这件事说死了:官方镜像包含 Playwright 的浏览器以及浏览器所需的系统依赖,但 Playwright 这个包本身不在镜像里,需要你单独安装。很多人第一次拉完镜像直接 docker run 然后发现命令不存在,就是没读到这句。
镜像里到底做了什么,utils/docker/Dockerfile.noble 写得比文档更直白。几处关键动作:
ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright,浏览器统一装在这个目录下,并且install -d -m 777 /ms-playwright /ms-playwright/.links把权限放开。adduser pwuser,镜像里预先建好了一个非 root 用户。- 先执行
playwright-core install-deps装系统依赖,再分别install chromium、install firefox、install webkit。文件里有一行注释说明这样拆分的目的是让各浏览器各占一层、可以并行拉取(仓库文档自述);另一行注释写明「installing chromium also installs chromium-headless-shell and ffmpeg」。 - 还调用了一个隐藏命令
playwright-core mark-docker-image,它对应packages/playwright-core/src/cli/installActions.ts里的markDockerImage(),最终落到packages/playwright-core/src/server/registry/dependencies.ts的writeDockerVersion(),把镜像名与驱动版本写进/ms-playwright/.docker-info。这个文件后面会用来报错,先记住。
另一个不在镜像里、但你迟早会撞上的是版本对齐。文档明确提醒要把镜像固定到具体版本,并写明:如果镜像里的 Playwright 版本与你项目里的版本对不上,Playwright 将无法定位浏览器可执行文件。
二、前置条件
- 语言绑定不共用同一个镜像仓库路径。
docs/src/docker.md里四种绑定各给了一条docker pull命令,JS 是mcr.microsoft.com/playwright,Python 是mcr.microsoft.com/playwright/python,.NET 是mcr.microsoft.com/playwright/dotnet,Java 是mcr.microsoft.com/playwright/java。拉错路径就没有对应绑定的运行时。 - 基础镜像必须是 glibc 系。 文档的 Alpine 小节写明:Firefox 与 WebKit 的浏览器构建基于 glibc,Alpine Linux 以及其它基于 musl 的发行版不受支持。想靠换 Alpine 底座压体积这条路直接堵死。
- 镜像用途有限定。 文档里有一个
:::info块写明,该镜像仅供测试与开发用途,不建议用它去访问不可信的网站。 - 标签对应不同 Ubuntu LTS 底座。 仓库当前发布的标签后缀有
noble、jammy、resolute三种,分别对应不同的 Ubuntu LTS 版本,具体对应关系以仓库文档为准。另外docs/src/docker.md的源码里镜像标签写的是v%%VERSION%%-noble这样的占位符,发布时才会替换成当次版本号——你直接照抄源码里的命令是跑不通的,占位符要换成你项目对应的版本。
三、用官方镜像:跑起来要加哪些参数
拉取(以 Python 绑定为例,其它绑定换成上面对应的路径):
docker pull mcr.microsoft.com/playwright/python:v%%VERSION%%-noble
运行这一步,文档按用途分成了两种写法,差别不是风格问题。默认情况下镜像用 root 用户跑浏览器,而这会禁用 Chromium 的 sandbox(root 下用不了)。文档给的判断标准是:跑的是你信任的代码(比如端到端测试),用 root 省事也行;做抓取、爬取这类会加载不可信页面的活,建议在容器里另建用户并配合 seccomp 配置。
信任场景:
docker run -it --rm --ipc=host mcr.microsoft.com/playwright/python:v%%VERSION%%-noble /bin/bash
不可信场景,切到镜像里预建的 pwuser 并挂上 seccomp 配置:
docker run -it --rm --ipc=host --user pwuser --security-opt seccomp=seccomp_profile.json mcr.microsoft.com/playwright/python:v%%VERSION%%-noble /bin/bash
那份 seccomp_profile.json 在仓库 utils/docker/seccomp_profile.json,文档说明它是 Docker 默认 seccomp 配置加上额外的 user namespace 克隆权限,多出来的就是允许 clone、setns、unshare 三个系统调用的一条 SCMP_ACT_ALLOW 规则。这三个调用正是 Chromium 拉起自己那层 sandbox 所需要的。
文档另外单列了一节推荐配置,三条:--init 用来避免 PID=1 进程的特殊待遇(僵尸进程的常见来源);用 Chromium 时推荐 --ipc=host,文档写明不加这个 Chromium 可能因内存不足而崩溃;启动 Chromium 报奇怪错误时,本地开发可以试 docker run --cap-add=SYS_ADMIN。注意最后这条文档限定的语境是「本地开发时」,不是让你在流水线上长期挂着。
以上为按仓库文档中的参数语义组合的示例,未经实测,以仓库最新内容与 --help 的实际输出为准。
四、自建镜像:需要补的其实就一条命令
如果你已经有一套自己的基础镜像,docs/src/docker.md 末尾给了两份最小 Dockerfile。JS 侧:
FROM node:20-bookworm
RUN npx -y playwright@%%VERSION%% install --with-deps
Python 侧是另一份,写法不同,别互相套用:
FROM python:3.12-bookworm
RUN pip install playwright==@%%VERSION%% && \
playwright install --with-deps
这两份 Dockerfile 里的 FROM 基础镜像标签是仓库文档当前给出的示例值,不是硬性要求,也会随文档更新变动,你按自己项目的运行时选。
核心就是 install --with-deps。docs/src/browsers.md 讲得更细:install-deps 单独装系统依赖,install --with-deps 把浏览器和系统依赖合成一条命令,两者都可以只针对某个浏览器,例如 playwright install --with-deps chromium。
想再压一层,还有 --only-shell。docs/src/browsers.md 的「Chromium: headless shell」一节写明,Playwright 为 headed 场景提供常规 Chromium 构建,为 headless 另有一份 chromium headless shell;如果你只跑 headless、且没有指定 channel 选项,可以在安装时加 --only-shell 跳过完整 Chromium 的下载。这个前提条件别漏,指定了 channel 就不适用。
系统依赖具体是哪些包,不必猜,packages/playwright-core/src/server/registry/nativeDeps.ts 里按平台列着,分成 chromium、firefox、webkit、tools 四组(对应 dependencies.ts 里的 DependencyGroup 类型)。安装动作由 installDependenciesLinux() 拼成 apt-get update 加 apt-get install -y --no-install-recommends ... 执行——所以自建镜像本质上就是替你跑了这两条 apt 命令,理解到这一层,出问题时你自己也能手动补。
五、边界:容器里跑 headed 是有条件的
这是最容易在流水线上翻车的一处。docs/src/ci.md 的「Running headed」一节写明:Playwright 默认以 headless 启动浏览器;在 Linux 代理机上,headed 执行需要安装 Xvfb,官方 Docker 镜像与官方 GitHub Action 已预装 Xvfb;要以 headed 跑,在实际命令前加 xvfb-run:
xvfb-run pytest
JS 侧对应的是 xvfb-run npx playwright test,Java 是 xvfb-run mvn test,.NET 是 xvfb-run dotnet test——同一节里四种绑定各给了一条,写哪种语言抄哪一条。Xvfb 之所以在镜像里,是因为它就在 nativeDeps.ts 的 tools 组第一位,install-deps 会带上它。
如果你想在容器里看见浏览器、甚至用 codegen 录用例,文档给的是 noVNC 路线:镜像内置了 noVNC 查看器,在 .devcontainer/devcontainer.json 里启用 desktop-lite feature 并指定 webPort,同时通过 forwardPorts 把端口透出容器,就能在浏览器标签页里打开这个 web 查看器,进而录制用例、拾取选择器、直接在容器上用 codegen。仓库示例里 webPort 与 forwardPorts 用的是同一个端口值,image 字段写的是一个钉死版本的镜像标签(本文不引具体版本号,填你项目对应的那个)。
其余几条边界,仓库里都写了,照实记着:
- 用 root 跑就等于关掉了 Chromium sandbox,这不是「有容器所以安全」,文档专门为不可信站点给了另一套跑法。
- Alpine / musl 不受支持,没有折中方案。
- 远程连接场景(在容器里跑 Playwright Server、测试留在宿主机)文档有单独一节,并附了一条
:::note:远程运行时要确保你测试里的 Playwright 版本与容器里运行的版本一致。想让容器访问宿主机上的本地服务,文档的做法是加--add-host=hostmachine:host-gateway,然后测试里用hostmachine代替localhost。 - Windows 侧要留一句:
install-deps在 Windows 上和 Linux 上不是一回事。packages/playwright-core/src/server/registry/dependencies.ts里的installDependenciesWindows()只在目标包含chromium时做事,动作是用powershell.exe -ExecutionPolicy Bypass -File install_media_pack.ps1。也就是说 Windows 上没有那一大串 apt 包的对应物。至于在 Windows 上使用这些镜像,镜像底座是 Ubuntu,属于 Linux 容器,这部分是通用的 Docker 运维常识、不是该项目官方内容,请按你本地 Docker 的实际配置处理。
六、怎么验证配对了
三个可执行的验证动作,都能回源:
一是 install-deps --dry-run。 packages/playwright-core/src/cli/program.ts 里这个选项的描述写明:不修改系统;在 Linux 上通过 apt-get 模拟安装,若有必需包缺失则以非零码退出;在 Windows 上则打印安装命令。帮助文本里还专门写了它「适用于非交互式的验证脚本」。自建镜像加一层 RUN playwright install-deps --dry-run 就能在构建期把缺包问题挡住。
二是看版本不匹配的报错文案。 packages/playwright-core/src/server/registry/index.ts 在找不到可执行文件时,会读 /ms-playwright/.docker-info,一旦发现镜像里记录的版本与当前驱动版本不一致,抛出的就不是普通的「请安装浏览器」,而是这样一段:
Looks like Playwright was just updated to <版本>.
Please update docker image as well.
- current: <当前镜像名>
- required: <所需镜像名>
看到 current / required 这两行,说明问题百分之百出在镜像标签与项目依赖版本没对齐,别再去查系统库了。反过来,如果报错是「Looks like Playwright was just installed or updated. Please run the following command to download new browsers」,那就是浏览器没装,跟镜像版本无关。
三是浏览器起不来时开启动日志。 docs/src/ci.md 写明 Playwright 支持 DEBUG 环境变量,排查 Error: Failed to launch browser 时把它设成 pw:browser:
DEBUG=pw:browser pytest
顺一遍就是:先确认镜像标签和你项目里的 Playwright 版本一致,再确认镜像路径选的是你那个语言绑定,然后按信任与否决定 root 还是 pwuser + seccomp,Chromium 场景补上 --ipc=host 与 --init,最后如果要 headed,记得 xvfb-run 打头。
本文依据 github.com/microsoft/playwright 仓库于 2026-08-18 的公开内容整理,
事实来自仓库内的文档与源码。我们没有对文中涉及的功能做过实测,
因此不涉及运行速度、稳定性与实际表现的任何描述。
该项目迭代频繁,文中涉及的 API 签名、配置项与默认值随版本变动,请以仓库最新内容为准。
本文不涉及浏览器版本矩阵与版本清单,相关信息请以官方发布说明为准。
本文对照的是同一项目内的两种用法,依据均为上述仓库内容,不对两种用法做优劣排名, 选型结论只在仓库文档写明的能力边界内成立。
安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。