Docker 中的多阶段构建是什么?

132 阅读6分钟

Docker 中的多阶段构建是什么? 多阶段构建允许您在单个 Dockerfile 中使用多个FROM语句来执行以下操作:

一次性构建应用程序🏗️ 仅将所需内容复制到较小的最终图像中📦 ❓ 我们为什么需要它? ✅主要目标:

🚀 好处 💬 为什么重要 ⚡ 较小的图像 仅将所需内容复制到最终图像中 🔐 更安全 生产镜像中没有开发工具或秘密 🛠️ 更清洁的 CI/CD 独立的构建和运行环境 📚 更好的层缓存 加快构建速度 🌍 环境分离 一张图片构建一切! 🧠 现实世界类比 想象:

🏗️ 第 1 阶段 = 施工现场(杂乱、沉重的工具) 🏠 第二阶段 = 房屋竣工(干净、舒适) 你在杂乱的环境中建造,但只把家具搬进干净的房子里。🧹

🧪 多阶段构建语法

🔨 Stage 1: Build Stage

FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build

📦 Stage 2: Final Production Image

FROM node:20-alpine WORKDIR /app

Copy only final build artifacts (no source or node_modules)

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

Set env vars, port and run

ENV PORT=3000 EXPOSE 3000 CMD ["node", "dist/index.js"] 🔍 关键概念解释 关键词 意义 AS builder 给这个阶段命名 --from=builder 从上一阶段复制文件 npm ci --omit=dev 仅安装生产部门 COPY . . 仅在构建阶段使用,以避免最终图像中的代码膨胀 📦 之前 vs 之后:图像大小 方法 图像大小 内容 😵‍💫 传统单人建造 ~900MB 完整源代码+开发依赖项 🤩 多阶段构建 约200MB 刚刚构建了应用程序+运行时依赖项 💥 真实项目示例:React App

Step 1: Build React App

FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm install COPY . . RUN npm run build

Step 2: Serve using NGINX

FROM nginx:alpine COPY --from=builder /app/build /usr/share/nginx/html EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] ✅ 这将使用 Node.js 构建应用程序,并通过 NGINX 提供静态文件(最终图像中没有 Node.js!)

🎯 常见的多阶段用例www.ysdslt.com 用例 描述 💻 前端构建 使用node+nginx组合 🔧 后端构建 使用 TS/Go/Rust 构建,然后仅复制二进制文件 🧪 测试阶段 在一个阶段添加测试/linting,在最终阶段跳过 📦CI/CD 管道 跨阶段的干净、可重复的构建 🧰 专业技巧和最佳实践 💡 提示 ✅ 推荐 使用--omit=dev 在最后阶段剥离仅限开发的软件包 使用.dockerignore 排除node_modules、.git、tests/等 使用标签 添加元数据,如版本、作者等 不要复制所有内容 使用精确COPY路径进行尺寸控制 使用命名阶段 --from=builder更容易从( )复制 保持最终图像最小化 足以运行您的应用程序(无需工具!) 🔄 与 Docker Compose 结合 您可以在 Dockerfile 中定义多阶段构建并运行:

docker-compose build docker-compose up 您的服务将自动使用优化的最终图像🧠✅

🛠️ TypeScript API 的多阶段示例

Stage 1: Compile TS

FROM node:20-alpine AS builder WORKDIR /app COPY . . RUN npm install RUN npm run build

Stage 2: Run with only JS output

FROM node:20-alpine WORKDIR /app COPY --from=builder /app/dist ./dist COPY --from=builder /app/package*.json ./ RUN npm ci --omit=dev CMD ["node", "dist/server.js"] ✅ 摘要:何时使用多阶段构建? ✅ 始终使用,如果:

您正在使用以下构建工具tsc:webpackvite 您想要最少的生产图像 您想要分离测试/准备/构建 您希望更快的 CI 构建和更小的攻击面 🧾 最终 TL;DR 备忘单 阶段 目的 基础镜像 输出 第一阶段(建造者) 构建、编译、测试 node,golang,rust, ETC。 /dist,/build, ETC。 第 2 阶段(生产) 仅服务/运行应用程序 node:alpine,nginx, ETC。 最终的瘦身形象 📦 完整 Dockerfile(上下文回顾) FROM node:20-alpine3.19 as base

Stage 1: Build Stuff

FROM base as builder

WORKDIR /home/build

COPY package*.json . COPY tsconfig.json .

RUN npm install

COPY src/ src/

RUN npm run build

Stage 2: Runner

FROM base as runner

WORKDIR /home/app

COPY --from=builder /home/build/dist dist/ COPY --from=builder /home/build/package*.json .

RUN npm install --omit=dev

RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nodejs

USER nodejs

EXPOSE 8000 ENV PORT=8000

CMD [ "npm", "start" ] 🎯 A2Z 各部分细分 🧱FROM node:20-alpine3.19 as base 🧠功能:

从最小的 Node.js 20 Alpine 镜像开始 Alpine 重量轻(约 5MB),适合用于小型、快速的图像 as base命名此阶段以供重复使用 🧩 想象一下base两个阶段使用的共享模板。

🔨第一阶段:建造者 FROM base as builder WORKDIR /home/build 🔧这里发生了什么:

我们切换到一个新的构建阶段,使用base图像 WORKDIR /home/build为我们的构建过程设置一个目录 COPY package*.json . COPY tsconfig.json . RUN npm install 📦安装依赖项:

package*.json复制到安装依赖项 tsconfig.jsonTypeScript 编译所必需的 npm install安装所有依赖项(dev + prod) COPY src/ src/ RUN npm run build 🛠️构建你的应用程序:

复制你的应用的 TypeScript 代码 npm run build将 TS 编译为 JS → 通常在内部/dist ✅ 第一阶段的最终结果: /home/build/dist包含已编译且可用于生产的 JS 输出的文件夹。

🚀第二阶段:跑步者 FROM base as runner WORKDIR /home/app 📁功能:

我们现在创建一个新的容器来运行该应用程序。 WORKDIR /home/app是您的应用程序运行的地方。 COPY --from=builder /home/build/dist dist/ COPY --from=builder /home/build/package*.json . 📂仅复制构建的工件:

仅复制dist/文件夹和包文件(无源,无 tsconfig) 确保最终图像纤薄、清晰 RUN npm install --omit=dev 🔐仅限生产安装:

仅安装产品依赖项(没有诸如、等开发eslint工具tsc) 保持最终图像明亮且安全✅ 👮 添加安全非 root 用户 RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nodejs USER nodejs 🔐为什么?

在容器中运行root很危险❌ 为了安全起见,我们创建权限有限nodejs的用户 UID/GID1001只是一个任意的非 root 系统用户 🌐 端口和环境设置 EXPOSE 8000 ENV PORT=8000 EXPOSE 8000:记录应用程序使用端口 8000 ENV PORT=8000:设置应用程序内部使用的默认端口 您仍然需要使用-p将其映射到主机: docker run -p 8000:8000

🚦启动应用程序 CMD [ "npm", "start" ] 🟢容器运行时的默认入口点

这将触发您的"start"脚本package.json: "start": "node dist/index.js" ✅ 汇总表 🔹 部分 🔍 目的 FROM base 重复使用图像以减少重复 builder 将 TypeScript 编译为 JS runner 运行最小生产镜像 npm install在建造者中 安装用于构建的完整依赖项 npm install --omit=dev在跑步者中 仅安装运行所需的内容 COPY --from=builder 无需重建的高效文件复制 USER nodejs 增强容器安全性 📊 由此带来的好处 🚀 好处 ✅ 已实现 小图像 ✅ 最终镜像中仅包含运行时代码 安全的 ✅ 非 root 用户,无开发工具 更快的构建 ✅ 重用构建层 干净的代码分离 ✅ 最终容器内没有 TypeScript 或构建文件 便携的 ✅ 可以在任何带有 Node 20 的平台上运行 🧠 额外提示:查看图片尺寸 docker images 比较多阶段图像(~100MB)与单阶段图像(~400-600MB) 🤯

🔚 最后的想法 这种方法遵循Docker 最佳实践:

多级 生产就绪 默认安全 可重复的构建