Docker 多阶段构建
多阶段构建让编译工具与运行镜像分离;用明确的工作目录、产物路径和依赖锁文件组织构建与缓存。
[!info] related notes
- 前置: Docker 核心概念
Docker 多阶段构建
核心思想
一个 Dockerfile 多个 FROM:builder 阶段包含编译工具和依赖,runtime 阶段只复制编译产物。
# Builder: Node.js + 源码 + 依赖 → 构建产物
FROM node:24-slim AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
# Runtime: 只有 nginx + 静态文件
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
本例假设项目提交 package-lock.json,build 产出静态站点 dist/;SSR 服务或仍需 Node 运行时的应用不能直接用 nginx 替代。这里的镜像标签用于示例,实际发布应固定并维护基础镜像版本/摘要。
镜像体积边界
运行镜像不会继承未复制的 builder 文件,但仍包含运行基础镜像和所复制产物。减小幅度取决于项目、架构、基础镜像及统计口径,不能把 1.2GB→30MB 当作保证;应比较实际镜像及其各层。
分层缓存优化
以下是 pnpm 项目的构建阶段片段:需先设置 WORKDIR,并安装/激活项目固定版本的 pnpm;不能假设 Node 基础镜像一定自带可用 pnpm。
# 1. 先复制依赖声明(变化少)
COPY package.json pnpm-lock.yaml ./
# 2. 安装依赖(这层被缓存)
RUN pnpm install --frozen-lockfile
# 3. 后复制源码(变化多)
COPY . .
# 4. 构建(源码变了才重新执行)
RUN pnpm build
只有该层指令、先前层、基础镜像以及复制的所有输入(包括锁文件)等缓存条件未变且缓存仍可用时,依赖安装层才可复用。workspace、补丁或 .npmrc 等影响安装的文件也需在这一层前复制,不能只检查 package.json。
Go 后端的多阶段构建
FROM golang:1.26-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /out/server ./cmd/server
FROM alpine:3.19
COPY --from=builder /out/server /usr/local/bin/server
ENTRYPOINT ["/usr/local/bin/server"]
本例要求存在 go.mod/go.sum 且 ./cmd/server 是 main 包。按包路径构建可包含同目录其他源文件;只指定 main.go 可能遗漏它们。CGO_ENABLED=0 适用于不依赖 cgo 的程序,但运行时仍可能需要 CA 证书、时区数据或配置文件。最终大小需实测;示例保留的 alpine:3.19 是旧版本引用,部署前应替换为受支持并经验证的基础镜像,不作为当前推荐版本。
常见错误
Windows 原生绑定
跨平台构建失败可能涉及原生依赖、可选包、包管理器版本或不完整锁文件。先在目标平台用锁定版本和冻结锁文件安装复现,再按依赖工具规则修复并提交锁文件;不要默认在 Dockerfile 中手动补包来掩盖依赖漂移。
.dockerignore 没配
确保排除 node_modules、.git、.env、已有构建产物等非构建输入;保留真正需要的源码和锁文件。只在最终阶段不复制秘密,并不意味着构建上下文和缓存里从未出现秘密。