一个 Dockerfile 的表象

大多数人对构建的认知停留在「写个 Dockerfile 然后 docker build 一下」。在小规模场景下这确实够用了。但当你管理的应用从几十个增长到几千个,构建系统需要解决的问题远比想象中复杂:基础镜像的统一维护、构建缓存的跨机器共享、安全扫描的集成、产物的版本管理和溯源。

我们在平台上线初期就低估了构建这件事的复杂度。第一版方案很简单——每个应用自己写 Dockerfile,平台只负责触发构建。三个月后问题就来了:基础镜像碎片化严重,安全漏洞修复推不动,构建时间参差不齐。


多阶段构建与缓存策略

Docker 的多阶段构建(multi-stage build)是管理构建复杂度的第一步。通过分离编译环境和运行环境,可以有效控制镜像体积。但真正难的是缓存策略——如何让构建系统智能地判断哪些步骤可以跳过。

我们踩过一个典型的坑:COPY . . 这一行会导致任何文件变更都使后续缓存失效。优化后改为先拷贝依赖声明文件(package.json、pnpm-lock.yaml),安装依赖后再拷贝源代码。这个看似简单的调整,在大规模场景下节省了数十万分钟的构建时间。


平台化构建的抽象层

最终我们走向了平台化构建的方案:业务方不再直接编写 Dockerfile,而是声明应用类型和构建参数,平台根据声明自动生成构建流程。这层抽象带来了几个好处:基础镜像可以统一升级、安全策略可以全局注入、构建最佳实践可以沉淀为模板。

当然,抽象是有代价的。总会有应用需要自定义构建步骤,平台需要提供足够的扩展点而不是一刀切。我们的方案是提供多个预设的「构建配方」(Build Recipe),同时允许在特定阶段注入自定义脚本。


复杂度不会消失,只会转移

构建系统的一个核心认知是:复杂度不会凭空消失,你只是选择让它出现在哪里。让每个团队自己管 Dockerfile,复杂度分散在各处;做平台化构建,复杂度集中到平台。后者的好处是可以由专业的团队统一治理,但同时也意味着平台团队需要深入理解各种技术栈的构建需求。