1. 为什么要在容器里塞进 Chrome、ChromeDriver 和 Java 8
把浏览器、驱动和运行时打包进同一个镜像,听起来像是把三种不相干的东西硬凑在一起,但做过自动化测试或网页采集的人一眼就能明白这个组合的用意:Java 8 跑测试框架或调度服务,Chrome 提供真实渲染环境,ChromeDriver 负责把两者接起来。这套组合在无头浏览器自动化、页面截图、前端回归测试、报表生成这类场景里出现频率极高。
问题在于,这三样东西的版本兼容性非常挑剔。Chrome 和 ChromeDriver 的主版本号必须严格对齐,差一个大版本就可能直接报 session 创建失败;Java 8 虽然稳定,但在较新的 Debian 基础镜像上安装时,证书、字体、依赖库经常出岔子。更麻烦的是,容器里没有图形界面,Chrome 默认起不来,必须靠无头模式和一堆启动参数兜底。这些细节如果不在构建镜像时一次性处理干净,后面每次跑任务都要重新踩一遍。
我见过太多团队的做法是:基础镜像随便拉一个,Dockerfile 里apt-get install一把梭,跑通了就提交,结果换台机器、换个时间重新构建就挂。根本原因是没搞清楚每个安装步骤背后的依赖关系,也没锁版本。这篇内容就是把这套镜像从零搭起来的完整过程拆开讲,包括 Debian 11 上的源配置、Chrome 与 ChromeDriver 的版本对齐策略、Java 8 的安装取舍、无头模式必需的依赖补齐,以及构建过程中那些文档里不会写的坑。适合正在做自动化测试镜像、网页采集服务容器化,或者单纯想把这三件套塞进一个可复现镜像的读者。
2. Debian 11 基础镜像的选择与源配置
2.1 为什么锁定 debian:11 而不是 slim 或最新版
基础镜像的选择直接决定了后面所有安装步骤的顺利程度。Debian 11(代号 bullseye)是目前兼容性比较稳的一个长期支持版本,glibc 版本、OpenSSL 版本和 Chrome 的官方依赖要求对得上。很多人图省空间用debian:11-slim,体积确实小了一大截,但 slim 版本裁掉了大量运行时库和文档,Chrome 运行所需的字体、图形库、NSS 相关组件基本都得手动补,补漏一个就是启动即崩。
我的建议是:构建阶段用完整版debian:11,如果最终镜像体积敏感,再用多阶段构建把产物挪到精简镜像里。但要注意,Chrome 本身依赖的共享库很多,精简镜像里照样得装回来,省不了多少。对于测试类镜像,体积通常不是第一优先级,稳定可复现才是。所以直接用FROM debian:11起步,别一上来就追求 slim。
还有一个细节:不要用debian:latest。latest 会随时间漂移到新的大版本,某天重新构建时基础环境变了,Chrome 装不上或者 Java 8 的依赖冲突,排查起来非常痛苦。锁死debian:11是保证可复现的第一道防线。
2.2 源配置:别让 apt 卡在第一步
Debian 11 默认的软件源在国内网络环境下经常慢到让人怀疑人生,构建时apt-get update卡几分钟甚至超时是常态。Dockerfile 里第一件该做的事就是把源换掉。这里要注意,Debian 11 的源配置文件在/etc/apt/sources.list,格式和旧版本略有不同,bullseye 已经支持deb822格式,但传统单行格式依然可用,改起来更直观。
一个稳妥的写法是在RUN里直接覆盖源文件,然后立刻apt-get update,把这两步放在同一个层里,避免缓存失效导致源和索引不一致:
RUN sed -i 's|deb.debian.org|mirrors.aliyun.com|g' /etc/apt/sources.list \ && sed -i 's|security.debian.org|mirrors.aliyun.com|g' /etc/apt/sources.list \ && apt-get update \ && apt-get install -y --no-install-recommends \ ca-certificates curl gnupg wget unzip \ && rm -rf /var/lib/apt/lists/*这里有几个经验点。第一,--no-install-recommends必须加,否则 apt 会顺带装一堆推荐包,镜像体积翻倍不说,还可能引入不必要的服务。第二,rm -rf /var/lib/apt/lists/*放在同一个RUN末尾,能实打实减小层体积,如果单独写一层是删不掉上一层缓存的。第三,ca-certificates和gnupg要提前装,后面添加 Chrome 的软件源签名时全靠它们,缺了会在apt-key或gpg环节报错。
提示:换源之后如果
apt-get update报签名或 Release 文件错误,多半是镜像源同步延迟或者源地址写错。可以先curl -I探一下源地址是否可达,再决定换哪个镜像站,不要盲目重试。
2.3 时区与 locale 的隐性影响
这一条很多人会忽略,但在实际跑自动化任务时影响不小。容器默认是 UTC 时区,如果测试用例或日志里涉及本地时间判断,时间对不上会导致断言失败或者日志时间线混乱。Java 8 对 locale 也比较敏感,某些日期格式化在缺少 locale 数据时会抛异常。
在 Dockerfile 里补上时区和 locale 配置,成本很低:
ENV TZ=Asia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \ && echo $TZ > /etc/timezone \ && apt-get install -y --no-install-recommends locales \ && sed -i 's/# en_US.UTF-8/en_US.UTF-8/' /etc/locale.gen \ && locale-gen ENV LANG=en_US.UTF-8 ENV LANGUAGE=en_US:en ENV LC_ALL=en_US.UTF-8locales包本身不大,但locale-gen生成的字符集数据对 Java 处理中文、处理非 ASCII 字符很关键。我遇到过没配 locale 的镜像里,Java 读取含中文的配置文件出现乱码,排查了半天才发现是容器 locale 是 POSIX。这种坑提前堵上,比事后查省事得多。
3. Chrome 与 ChromeDriver 的版本对齐与安装
3.1 版本对齐是这套镜像的生命线
Chrome 和 ChromeDriver 的版本关系不是"差不多就行",而是主版本号必须一致。Chrome 114 配 ChromeDriver 113,启动时直接报session not created: This version of ChromeDriver only supports Chrome version 113。更隐蔽的情况是主版本一致但次版本差异较大,某些新特性或安全策略行为不同,测试结果会飘。
所以安装策略只有一条:先确定 Chrome 版本,再装对应版本的 ChromeDriver,两者在 Dockerfile 里用同一个版本变量控制。不要一个用 apt 装最新、一个去官网下最新,两条独立的"最新"路径很可能对不上。
具体做法是定义一个构建参数,把版本号显式写死:
ARG CHROME_VERSION=114.0.5735.198 ARG CHROMEDRIVER_VERSION=114.0.5735.90Chrome 的版本可以从官方 stable 仓库的 Packages 文件里查到,ChromeDriver 的版本在它的版本索引页里按主版本号对应。这里要注意,ChromeDriver 的版本号不一定和 Chrome 完全一致,只要主版本号相同即可,次版本差异通常兼容。但为了保险,尽量选同一主版本下发布时间接近的组合。
3.2 用官方 deb 包安装 Chrome 而不是 apt 源
安装 Chrome 有两条路:添加 Google 的 apt 源然后apt-get install google-chrome-stable,或者直接下载 deb 包用dpkg -i安装。前者的问题是源里的版本会随时间更新,今天构建和下周构建拿到的版本可能不同,破坏可复现性;后者可以精确锁定版本。
我倾向直接下载 deb 包:
RUN wget -q -O /tmp/chrome.deb \ https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb \ && apt-get install -y --no-install-recommends /tmp/chrome.deb \ && rm /tmp/chrome.deb注意这里用apt-get install装本地 deb 而不是dpkg -i,因为apt-get会自动解决依赖,dpkg -i遇到缺依赖只会报错停下,还得再apt-get install -f补,多一步且容易出错。另外google-chrome-stable_current_amd64.deb这个文件名里的 "current" 意味着它始终指向最新稳定版,所以严格来说这个 URL 本身不锁版本。如果要真正锁死,得去官方仓库的 pool 目录里找带完整版本号的文件名。对于大多数场景,用 current 包配合构建缓存问题不大,但如果对可复现性要求极高,建议把 deb 包提前下载好放进构建上下文或者内部制品库。
3.3 ChromeDriver 的下载与权限处理
ChromeDriver 是个独立的二进制文件,下载解压后放到/usr/local/bin即可:
RUN wget -q -O /tmp/chromedriver.zip \ https://storage.googleapis.com/chrome-for-testing-public/${CHROMEDRIVER_VERSION}/linux64/chromedriver-linux64.zip \ && unzip /tmp/chromedriver.zip -d /tmp/ \ && mv /tmp/chromedriver-linux64/chromedriver /usr/local/bin/chromedriver \ && chmod +x /usr/local/bin/chromedriver \ && rm -rf /tmp/chromedriver.zip /tmp/chromedriver-linux64这里有个容易踩的坑:新版 ChromeDriver 的下载包解压后多了一层chromedriver-linux64目录,老版本是直接一个chromedriver文件。如果照着旧教程写mv /tmp/chromedriver /usr/local/bin/,会报找不到文件。下载前先确认包结构,或者用unzip -l看一眼再写移动命令。
chmod +x不能省。有些解压方式会丢掉可执行权限,ChromeDriver 没有执行权限时,Selenium 启动会报Permission denied,错误信息不明显,容易误判成路径问题。
注意:ChromeDriver 的下载地址域名和 Chrome 的 deb 包域名不同,如果构建环境对出网有白名单限制,两个域名都要放行,否则会在其中一步卡住。
3.4 验证安装是否真的可用
装完不等于能用。在 Dockerfile 末尾加一步验证,能在构建阶段就暴露问题,而不是等到运行容器时才发现:
RUN google-chrome --version && chromedriver --versiongoogle-chrome --version在无头环境下能正常输出版本号,说明二进制和基础依赖没问题。chromedriver --version输出里会带上它支持的 Chrome 版本,肉眼核对一下主版本号是否一致。这一步花不了几秒,但能挡掉一大类"镜像构建成功、运行即失败"的问题。
4. Java 8 在 Debian 11 上的安装取舍
4.1 Debian 11 默认源里没有 openjdk-8
这是很多人第一次在 bullseye 上装 Java 8 时撞的墙。Debian 11 的官方源里提供的是 OpenJDK 11 和 17,apt-get install openjdk-8-jdk会直接报找不到包。原因很简单,Java 8 已经过了 Debian 的维护周期,被移出了稳定版源。
解决办法有几种,各有取舍:
| 方案 | 优点 | 缺点 |
|---|---|---|
| 添加旧版源(buster) | 安装简单,apt 管理 | 混源有依赖风险,不推荐 |
| 下载 Adoptium/Temurin 的 tar.gz | 版本可控,干净 | 需手动配置环境变量 |
| 用 SDKMAN 安装 | 灵活,多版本切换 | 镜像里多一层工具,体积增加 |
| 换基础镜像为带 Java 8 的镜像 | 省事 | 失去对基础环境的控制 |
我推荐下载 Temurin 的 tar.gz 手动解压。它不依赖 apt 源,版本完全可控,解压后配好JAVA_HOME和PATH就能用,干净利落。混用 buster 源虽然能装上,但会把旧版的库依赖带进来,和 bullseye 的其他包产生冲突的概率不低,属于给自己埋雷。
4.2 手动安装 Temurin JDK 8 的完整步骤
先从 Adoptium 的发布地址拿到对应架构的压缩包。x64 架构的包名类似OpenJDK8U-jdk_x64_linux_hotspot_8u372b07.tar.gz,版本号里的8u372b07是具体的构建号,建议锁死这个构建号而不是用 latest:
ARG JDK_VERSION=8u372b07 RUN mkdir -p /opt/java \ && wget -q -O /tmp/jdk.tar.gz \ https://github.com/adoptium/temurin8-binaries/releases/download/jdk8u372-b07/OpenJDK8U-jdk_x64_linux_hotspot_8u372b07.tar.gz \ && tar -xzf /tmp/jdk.tar.gz -C /opt/java --strip-components=1 \ && rm /tmp/jdk.tar.gz ENV JAVA_HOME=/opt/java ENV PATH=$JAVA_HOME/bin:$PATH--strip-components=1的作用是去掉压缩包里的顶层目录,直接把内容解到/opt/java下,这样JAVA_HOME指向的路径更干净,不用带一长串版本目录名。如果不加这个参数,解压后会是/opt/java/jdk8u372-b07/,JAVA_HOME就得跟着改,后续维护容易忘。
环境变量用ENV设置而不是写进.bashrc。容器里跑的大多是exec形式的进程,不经过 shell 初始化,.bashrc里的配置根本不生效。ENV设置的变量对所有进程可见,这才是容器里配环境变量的正确姿势。
4.3 验证 Java 与字体依赖
装完 Java 后验证一下:
RUN java -version && javac -version输出里应该能看到1.8.0_372这样的版本号。如果报java: command not found,检查PATH是否包含$JAVA_HOME/bin,以及JAVA_HOME路径是否和实际解压目录一致。
另外,Java 8 本身不需要额外字体,但如果你的 Java 程序会生成图片、报表或者调用 AWT/Swing 相关功能,容器里缺字体会导致中文显示成方块或者直接抛FontConfiguration异常。Chrome 渲染页面同样需要字体,所以字体这步在下一节统一处理,这里先记着 Java 侧也有这个需求。
5. 无头模式运行所需的系统依赖补齐
5.1 Chrome 在容器里起不来的真正原因
Chrome 在无图形界面的容器里启动,最常见的报错是Failed to launch chrome! ... No usable sandbox!或者error while loading shared libraries: libXXX.so。前者是沙箱机制在容器里受限,后者是缺共享库。这两个问题必须在构建镜像时解决,不能指望运行时临时处理。
沙箱问题的标准解法是加--no-sandbox启动参数,但这只是绕过,不是根治。更稳妥的做法是同时加--disable-dev-shm-usage,因为容器默认的/dev/shm只有 64MB,Chrome 渲染复杂页面时共享内存不够会直接崩溃,报错信息往往是session deleted because of page crash,非常误导人。
共享库缺失则要靠安装依赖包解决。Chrome 官方文档列了一长串依赖,在 Debian 11 上可以这样一次性补齐:
RUN apt-get update && apt-get install -y --no-install-recommends \ fonts-liberation \ fonts-noto-cjk \ libasound2 \ libatk-bridge2.0-0 \ libatk1.0-0 \ libatspi2.0-0 \ libcairo2 \ libcups2 \ libdbus-1-3 \ libdrm2 \ libgbm1 \ libglib2.0-0 \ libgtk-3-0 \ libnspr4 \ libnss3 \ libpango-1.0-0 \ libx11-6 \ libxcb1 \ libxcomposite1 \ libxdamage1 \ libxext6 \ libxfixes3 \ libxkbcommon0 \ libxrandr2 \ xdg-utils \ && rm -rf /var/lib/apt/lists/*这份清单里,libnss3、libatk-bridge2.0-0、libgbm1、libxkbcommon0是最容易漏的几个,漏了就是启动即崩。fonts-noto-cjk是中文渲染的关键,没有它,截图里的中文全是豆腐块。fonts-liberation提供基础西文字体,避免默认字体缺失导致的排版异常。
5.2 用 ldd 反查缺失的共享库
依赖清单再全,也可能因为 Chrome 版本更新引入新的库依赖。与其猜,不如用ldd直接查:
ldd /opt/google/chrome/chrome | grep "not found"这条命令列出 Chrome 二进制所有未满足的动态链接依赖。在 Dockerfile 里可以加一步验证,构建时如果输出非空就说明还缺库:
RUN ldd /opt/google/chrome/chrome | grep "not found" && exit 1 || true这个写法有点绕:grep找到 "not found" 时返回 0,&& exit 1让构建失败;没找到时grep返回非 0,走|| true让整条命令成功。这样构建阶段就能拦住缺库问题,而不是等运行时报错。
5.3 启动参数的最佳组合
依赖装齐之后,Chrome 的启动参数决定了它在容器里的稳定性。经过多次实测,下面这组参数覆盖了绝大多数场景:
--headless=new --no-sandbox --disable-dev-shm-usage --disable-gpu --window-size=1920,1080 --disable-extensions --disable-background-networking--headless=new是新版无头模式,比老的--headless更接近真实浏览器行为,渲染结果和截图质量更好。--disable-gpu在无 GPU 的容器里避免 GPU 初始化失败。--window-size显式指定视口大小,否则默认 800x600,截图和布局断言容易出偏差。--disable-background-networking减少后台请求,让测试更稳定。
这些参数在 Selenium 里通过ChromeOptions传入,Java 代码大致是这样:
ChromeOptions options = new ChromeOptions(); options.addArguments("--headless=new"); options.addArguments("--no-sandbox"); options.addArguments("--disable-dev-shm-usage"); options.addArguments("--disable-gpu"); options.addArguments("--window-size=1920,1080"); WebDriver driver = new ChromeDriver(options);提示:
--no-sandbox会降低浏览器隔离性,仅在受控的容器环境里使用。如果镜像会跑不可信页面,需要评估这个取舍,或者改用其他隔离手段。
6. 构建过程中的踩坑链路与排查方法
6.1 构建成功但容器一跑就崩的排查顺序
镜像docker build成功,docker run启动 Chrome 却失败,这是最让人抓狂的情况。我的排查顺序是固定的,按这个链路走基本能定位到根因:
第一步,进容器手动跑google-chrome --version。如果这步就失败,说明二进制或基础依赖有问题,用ldd查缺库。第二步,跑google-chrome --headless --no-sandbox --dump-dom about:blank,看能否输出 DOM。这步失败通常是沙箱或共享内存问题。第三步,跑chromedriver --version确认驱动版本。第四步,用一段最小 Java 代码创建 session,看报错信息。
这个顺序的逻辑是从底层往上排:先确认浏览器本身能跑,再确认无头模式能跑,再确认驱动版本对,最后才验证 Java 调用链。跳过任何一步都可能把问题归错因。
6.2 几个高频报错与对应处理
| 报错信息 | 根因 | 处理方式 |
|---|---|---|
No usable sandbox! | 容器内沙箱受限 | 加--no-sandbox |
session deleted because of page crash | /dev/shm太小 | 加--disable-dev-shm-usage |
This version of ChromeDriver only supports Chrome version X | 版本不匹配 | 对齐主版本号 |
error while loading shared libraries: libnss3.so | 缺依赖库 | 安装libnss3等 |
Permission denied执行 chromedriver | 缺执行权限 | chmod +x |
| 中文显示为方块 | 缺中文字体 | 安装fonts-noto-cjk |
这张表里的每一条都是实际构建中反复出现的。尤其是session deleted because of page crash,报错信息完全没提共享内存,新手很容易往内存不足或者页面代码问题上想,实际加个启动参数就解决了。
6.3 镜像层缓存导致的"幽灵问题"
Docker 的层缓存是把双刃剑。改了 Dockerfile 里靠后的指令,前面的层全部复用,构建很快;但如果改了源配置或者版本变量,而缓存没失效,就会出现"明明改了却还是老样子"的幽灵问题。
典型场景:把CHROME_VERSION从 113 改成 114,但下载 deb 包那一步用的是current地址,缓存命中后根本没重新下载,装进去的还是旧版本。解决办法是在版本变量变化时强制让相关层失效,比如把版本号写进RUN命令的注释或者用一个ARG参与命令:
ARG CHROME_VERSION RUN echo "Installing Chrome ${CHROME_VERSION}" \ && wget -q -O /tmp/chrome.deb ... \ && apt-get install -y /tmp/chrome.debecho里带了版本变量,变量一变,这层的缓存键就变了,Docker 会重新执行。这是个低成本但很有效的技巧,避免版本更新时被缓存坑。
7. 镜像瘦身与最终 Dockerfile 组织
7.1 清理策略:哪些能删、哪些不能删
镜像体积主要来自三块:apt 缓存、下载的安装包、Chrome 和 JDK 本身。apt 缓存用rm -rf /var/lib/apt/lists/*清掉,下载的 deb、zip、tar.gz 在安装完立刻删,这两块能省几百 MB。Chrome 和 JDK 是运行时必需,删不得。
有一个容易误删的地方:/opt/google/chrome/下的某些文件看起来像文档或示例,但可能是运行时依赖。清理前用ldd确认没有引用,或者干脆不碰 Chrome 安装目录,只清临时文件。JDK 里的src.zip和man目录可以删,能省几十 MB,但jre相关目录一个都不能动。
7.2 多阶段构建是否值得
对于这套三件套镜像,多阶段构建的收益有限。Chrome 和 JDK 都是运行时必需,没法像编译型语言那样把构建工具留在前一阶段。唯一能挪走的是下载和安装过程中的临时文件,但这些在单阶段里用rm也能清掉。所以我的做法是单阶段构建,把每个RUN的清理动作写在同一层里,效果和多阶段差不多,Dockerfile 还更简单。
如果确实要压体积,可以考虑把 Chrome 换成chromium包(Debian 源里有),体积小一些,但版本更新节奏和 ChromeDriver 的匹配需要额外验证,稳定性不如官方 Chrome。这个取舍看具体场景,测试环境追求稳定就用 Chrome,对体积极度敏感再考虑 chromium。
7.3 完整的 Dockerfile 骨架
把前面各节的内容串起来,最终结构大致是这样:
FROM debian:11 ARG CHROME_VERSION=114.0.5735.198 ARG CHROMEDRIVER_VERSION=114.0.5735.90 ARG JDK_VERSION=8u372b07 ENV TZ=Asia/Shanghai ENV LANG=en_US.UTF-8 ENV LC_ALL=en_US.UTF-8 # 换源 + 基础工具 RUN sed -i 's|deb.debian.org|mirrors.aliyun.com|g' /etc/apt/sources.list \ && apt-get update \ && apt-get install -y --no-install-recommends \ ca-certificates curl wget unzip gnupg locales \ && sed -i 's/# en_US.UTF-8/en_US.UTF-8/' /etc/locale.gen \ && locale-gen \ && ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \ && echo $TZ > /etc/timezone \ && rm -rf /var/lib/apt/lists/* # Chrome 运行依赖 + 字体 RUN apt-get update && apt-get install -y --no-install-recommends \ fonts-liberation fonts-noto-cjk libasound2 libatk-bridge2.0-0 \ libatk1.0-0 libatspi2.0-0 libcairo2 libcups2 libdbus-1-3 \ libdrm2 libgbm1 libglib2.0-0 libgtk-3-0 libnspr4 libnss3 \ libpango-1.0-0 libx11-6 libxcb1 libxcomposite1 libxdamage1 \ libxext6 libxfixes3 libxkbcommon0 libxrandr2 xdg-utils \ && rm -rf /var/lib/apt/lists/* # Chrome RUN echo "Chrome ${CHROME_VERSION}" \ && wget -q -O /tmp/chrome.deb \ https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb \ && apt-get install -y --no-install-recommends /tmp/chrome.deb \ && rm /tmp/chrome.deb # ChromeDriver RUN wget -q -O /tmp/chromedriver.zip \ https://storage.googleapis.com/chrome-for-testing-public/${CHROMEDRIVER_VERSION}/linux64/chromedriver-linux64.zip \ && unzip /tmp/chromedriver.zip -d /tmp/ \ && mv /tmp/chromedriver-linux64/chromedriver /usr/local/bin/chromedriver \ && chmod +x /usr/local/bin/chromedriver \ && rm -rf /tmp/chromedriver.zip /tmp/chromedriver-linux64 # JDK 8 RUN mkdir -p /opt/java \ && wget -q -O /tmp/jdk.tar.gz \ https://github.com/adoptium/temurin8-binaries/releases/download/jdk8u372-b07/OpenJDK8U-jdk_x64_linux_hotspot_8u372b07.tar.gz \ && tar -xzf /tmp/jdk.tar.gz -C /opt/java --strip-components=1 \ && rm /tmp/jdk.tar.gz ENV JAVA_HOME=/opt/java ENV PATH=$JAVA_HOME/bin:$PATH # 验证 RUN google-chrome --version \ && chromedriver --version \ && java -version \ && ldd /opt/google/chrome/chrome | grep "not found" && exit 1 || true这个骨架可以直接用,版本变量按需调整。验证那一步的ldd检查是最后一道保险,构建时就能发现缺库,不用等运行。
8. 实际使用中的几点经验补充
镜像构建出来只是开始,真正跑起来还有几个细节值得注意。第一,Chrome 在容器里跑久了会积累临时文件,/tmp和用户数据目录会膨胀,建议在启动脚本里定期清理,或者给 Chrome 指定一个独立的--user-data-dir并挂载 tmpfs。第二,并发跑多个 Chrome 实例时,每个实例要独立的用户数据目录,否则会互相锁文件导致启动失败。第三,Java 8 的堆内存默认值在容器里可能按宿主机内存算,容易 OOM,启动时显式加-Xmx限制。
还有一点关于版本维护:Chrome 的更新节奏很快,每隔几周就有新版本,ChromeDriver 跟着更新。如果镜像用于长期运行的服务,建议把版本变量集中管理,定期同步更新 Chrome 和 ChromeDriver 的主版本号,别让镜像里的版本落后太多,否则访问某些新特性页面时渲染结果会和预期不符。更新时记得同时改两个版本变量,并利用前面说的缓存失效技巧确保真的重新下载了。
这套镜像我在几个自动化测试项目里用了挺长时间,稳定性不错,关键是每个环节的依赖和版本关系都理清楚了,出问题能快速定位到是哪一层。构建镜像这件事,把"为什么"想明白,比把命令抄对更重要。