核心是构建与运行物理隔离:用AS命名builder阶段(如FROM golang:1.22-alpine AS builder),final阶段用FROM alpine:3.20或scratch;builder仅复制源码、装依赖、编译,final仅COPY确定路径产物,并通过.dockerignore和--build-arg加固,最后实测验证无源码、工具及多余文件。

核心就一条:构建和运行必须物理隔离,源码和工具链只允许出现在 builder 阶段,且不能以任何形式漏进 final 镜像。
明确划分两个阶段并命名
用 AS 给构建阶段起名,比如 FROM golang:1.22-alpine AS builder,再用 FROM alpine:3.20 或 FROM scratch 开始运行阶段。命名不是可选操作,而是后续 COPY --from=builder 的前提。别用数字索引(如 --from=0),容易因阶段增减出错。
构建阶段只做必要动作
在 builder 阶段里,复制源码、安装依赖、执行编译即可,不做任何与运行相关的事:
- Go 项目加
CGO_ENABLED=0 GOOS=linux go build -a -o myapp .,生成静态二进制 - Node.js 项目用
RUN npm ci --only=production或npm run build,避免npm install带入 devDependencies - Java 项目用 Maven 构建后,只保留
target/*.jar,不复制src/、pom.xml、target/classes/
运行阶段只复制确定路径的产物
final 阶段不用 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 /usr/src/app/(仅需 runtime 依赖) - Java:
COPY --from=builder /app/target/myapp.jar /app.jar
复制完立刻检查路径是否存在多余内容,比如 /go、/node_modules、/root/.m2 —— 这些都不该出现。
配合 .dockerignore 和构建参数加固
.dockerignore 文件要排除所有非必需项:
- 必加:
.git/、node_modules/、src/、test/、logs/、*.md、Makefile - Go 项目可加:
go.sum(非运行必需)、vendor/(若启用 modules)
构建时用 --build-arg BUILD_ENV=prod 控制行为,例如 Node.js 中跳过 devDependencies 安装,Go 中加 -ldflags="-s -w" 去调试符号。
验证是否真正剔除成功
别只看 docker images 大小,要实测镜像内容:
-
docker run --rm -it your-image ls -la /—— 确认没有/app/src、/go、/usr/local/go -
docker run --rm -it your-image which gcc npm javac—— 所有命令应返回空 -
docker run --rm -it your-image find / -name "*.go" -o -name "*.java" -o -name "*.py" 2>/dev/null—— 不应输出任何路径
运行起来能工作,又查不到源码和工具,才算真正完成剔除。











