多阶段构建必须物理隔离构建与运行阶段,禁用数字索引而显式命名阶段(如as builder),仅复制确定路径的编译产物,严禁编译工具链进入最终镜像,并通过.dockerignore和--build-arg强化安全控制。

核心就一条:构建阶段和运行阶段必须物理隔离,编译工具链绝不能出现在最终镜像里。靠单个 FROM 搞不定,得用多阶段构建,且每个阶段职责清晰、边界明确。
必须命名构建阶段,禁用数字索引
第一阶段要用 AS builder 显式命名,比如:
FROM golang:1.22-alpine AS builderFROM node:20-slim AS frontend-builderFROM maven:3.9-openjdk-17 AS java-builder
后续复制必须用 COPY --from=builder,不能写 --from=0。一旦中间加个阶段(比如加个 lint 阶段),数字索引就会错位,产物拷错位置——这是线上事故的常见诱因。
构建阶段只做三件事:复制源码 → 装构建依赖 → 执行编译
不装运行时不需要的东西,不保留中间文件,不执行任何与运行相关的操作:
- Go 项目:设
CGO_ENABLED=0 GOOS=linux,用go build -a -ldflags="-s -w"生成静态二进制 - Node.js 项目:用
RUN npm ci --only=production或RUN npm run build,跳过devDependencies - Java 项目:Maven 构建后,只留
target/*.jar,删掉src/、pom.xml、target/classes/等非运行必需项
运行阶段只复制确定路径的产物,不带任何“多余层”
禁止宽泛复制,比如 COPY . 或 COPY --from=builder /app/ .。必须精确到文件或最小目录:
- Go:
COPY --from=builder /app/myapp /usr/local/bin/myapp - Node.js:
COPY --from=builder /app/dist /usr/src/app/dist,再单独COPY --from=builder /app/package.json . - Java:
COPY --from=builder /app/target/myapp.jar /app.jar
复制完立刻验证:运行容器后执行 ls /go /usr/local/go /node_modules /root/.m2,这些路径一个都不能存在——它们是编译环境残留的明确信号。
配合 .dockerignore 和构建参数加固防线
.dockerignore 是构建上下文的第一道闸门,必须排除所有非运行必需项:
- 必加:
.git/、node_modules/、src/、test/、logs/、*.md、Makefile - Go 项目建议加:
go.sum(非运行必需)、vendor/(若启用 modules) - 构建时用
--build-arg控制行为,例如:--build-arg BUILD_ENV=prod触发生产构建逻辑,跳过调试工具安装
不复杂但容易忽略。











