不喜欢广告? 无广告 今天

Docker 多阶段构建 在不破坏部署的情况下缩小镜像大小

更新于

多阶段Docker构建的实用指南——实际的构建前后镜像大小对比,适用于Node.js和Python的Dockerfile示例,以及每个团队至少会遇到一次的常见陷阱。

Docker 多阶段构建:在不破坏部署的情况下缩小镜像大小 1
广告 移除?

您的 Node.js 镜像大小为 1.1 GB。您已添加 .dockerignore,已修剪开发依赖项,尝试 node:slim ——几乎没有变化。真正的解决方案是多阶段构建。如果您尚未切换,那么您正在将 TypeScript 编译器发送到生产环境。

多阶段构建自 Docker 17.05 版本(2017 年)以来就已存在,但使用率却很低。以下是一个真实的操作流程:具体发生了什么变化、差异有多大,以及在首次迁移时会遇到的三个陷阱。

单阶段构建的问题

大多数 Dockerfile 都以如下方式开始:

FROM node:20

WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

EXPOSE 3000
CMD ["node", "dist/server.js"]

构建并使用 docker images: ~1.1 GB。您正在发送完整的 Node 20 镜像,包括 npm、TypeScript 工具链、所有开发依赖项以及完整的源代码树。在生产环境中这些内容都不会运行——应用程序只需要编译后的 dist/ 输出和少量运行时包。

多阶段构建:解决方案

每个 FROM 指令都会启动一个具有干净文件系统的全新阶段。使用 AS命名阶段,然后使用 COPY --from=stagename 将特定文件拉取到下一个阶段。中间阶段不会进入最终镜像——它们是构建产物,在构建完成后即被丢弃。 COPY 构建完成后。

以下是同一应用的正确多阶段构建版本:

# ---- Build stage ----
FROM node:20 AS builder

WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build


# ---- Runtime stage ----
FROM node:20-alpine AS runtime

WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist

EXPOSE 3000
CMD ["node", "dist/server.js"]

关键行: COPY --from=builder /app/dist ./dist。该指令将 分割 构建输出从构建阶段拉取到基于 Alpine 的运行时镜像中。TypeScript 编译器、源文件和开发依赖项从未触及最终层。

结果: ~160 MB 而不是 1.1 GB。对于典型的 Node 应用来说,这大约减少了 85% 的大小——而这是同一个构建产物,只是去除了周围的支撑结构。

添加一个测试阶段

您可以在构建和运行时之间添加一个测试阶段,以运行您的测试套件。如果测试失败,构建会在运行时镜像创建之前停止。如果测试通过,则在生产构建时完全跳过测试阶段。

FROM node:20 AS builder

WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build


FROM builder AS tester

RUN npm test


FROM node:20-alpine AS runtime

WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist

EXPOSE 3000
CMD ["node", "dist/server.js"]

在 CI 中,您会明确针对测试阶段: docker build --target tester .。对于生产镜像,您不指定目标,Docker 会按顺序运行所有阶段,并在最后 FROM处停止。测试阶段会运行,但其文件系统会被丢弃——测试起到一个门禁作用,而不是作为实际负载。

Python:相同理念,略有不同执行方式

Python 的多阶段构建遵循相同模式。主要区别在于:当您使用 /root/.local 时,pip 会在 --user下安装包,因此您会将该目录复制到精简的运行时镜像中。

FROM python:3.12 AS builder

WORKDIR /app
COPY requirements.txt .
RUN pip install --user -r requirements.txt
COPY . .


FROM python:3.12-slim AS runtime

WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY --from=builder /app .

ENV PATH=/root/.local/bin:$PATH

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

python:3.12 基础镜像:~1 GB。 python:3.12-slim 仅安装依赖项后:~180–250 MB,具体取决于 requirements.txt 中的内容。编译后的 .pyc 文件免费随行,因为它们与源代码相邻。

三个会让人至少一次遇到的陷阱

1. 复制错误的文件

最常见的错误:您 COPY --from=builder /app ./ 而不是 COPY --from=builder /app/dist ./dist。您只是复制了所有内容——源文件、测试固定文件、node_modules 等——进入了所谓的“最小”运行时镜像。现在它的大小甚至比单阶段版本还大。

明确说明您要复制的内容。只复制生产入口点实际需要的目录或文件。对于大多数 Node 应用:编译输出(dist/)和可选的静态资源。对于 Python:已安装的包和应用程序代码,而不是 requirements.txt、测试文件或笔记本文件。

2. 构建密钥泄露到层中

如果您将密钥作为构建参数传递(例如, ARG NPM_TOKEN ,然后在 RUN 命令中使用它),那么该密钥会出现在所有后续层中——即使在多阶段构建中也是如此。 docker history myimage 将显示它。

正确的方法是使用 Docker BuildKit 的 --mount=type=secret:

RUN --mount=type=secret,id=npm_token     NPM_TOKEN=$(cat /run/secrets/npm_token) npm ci

。该密钥仅在该层运行时挂载——它永远不会被提交到镜像历史中。使用: docker build --secret id=npm_token,src=.npmrc .

。人们常用的廉价补救方法——在同一个 RUN 层中删除密钥——并不能真正解决多阶段构建问题,但使用 BuildKit 的方法更干净。

3. 忘记使用 .dockerignore

多阶段构建可以缩小最终镜像,但 COPY . . 在构建阶段仍会将您的整个上下文发送给 Docker 服务。如果没有 .dockerignore,这包括 .git/, node_modules/、测试固定文件、本地 .env 文件以及您以明文文件形式存储的任何密钥。构建阶段会看到所有这些内容。

最小 .dockerignore 适用于任何 Node 项目:

.git
node_modules
dist
.env
*.log
coverage
.nyc_output

添加 .dockerignore 在您添加 Dockerfile的同一天。构建上下文大小会出现在 docker build 输出的第一行(Sending build context to Docker daemon X MB)——如果这个数字异常大,请检查包含的内容。

Dockerfile 工作的实用工具

如果您希望在编写自己的 Dockerfile 之前有一个起点,可以使用 Dockerfile 生成器 在 IO Tools 上为常见堆栈生成多阶段 Dockerfile。一旦您编写了内容,就使用 Dockerfile Linter & Formatter 来在进入 CI 之前发现常见错误——例如缺少 WORKDIR、使用 latest 标签或不必要的以 root 身份运行。

要点

多阶段构建是一个两步变化:添加一个命名的构建阶段,将编译输出复制到一个全新的最小镜像中。大小减少几乎总是值得的——对于 Node 和 Python 应用来说,典型的减少幅度为 80–90%。主要的陷阱是复制范围过宽、将密钥作为构建参数泄露,以及跳过 COPY --from。解决这些问题后,您就拥有了真正适合生产环境的镜像。 .dockerignore. 修复这些问题,你将得到一个真正适合生产环境的生产镜像。

想要享受无广告的体验吗? 立即无广告

安装我们的扩展

将 IO 工具添加到您最喜欢的浏览器,以便即时访问和更快地搜索

添加 Chrome 扩展程序 添加 边缘延伸 添加 Firefox 扩展 添加 Opera 扩展

记分板已到达!

记分板 是一种有趣的跟踪您游戏的方式,所有数据都存储在您的浏览器中。更多功能即将推出!

广告 移除?
广告 移除?
广告 移除?

新闻角 包含技术亮点

参与其中

帮助我们继续提供有价值的免费工具

给我买杯咖啡
广告 移除?