Dockerfile多阶段构建用途

1. 典型的“构建 + 运行”模式(Builder Pattern)——减小最终镜像体积

把编译、打包、依赖安装放在一个“构建”阶段,最终镜像只取运行时产物(无编译工具链)。

1
2
3
4
5
6
7
8
9
10
11
# 构建阶段
FROM golang:1.21-alpine AS builder
WORKDIR /app
ENV CGO_ENABLED=0
COPY . .
RUN go build -o myapp ./cmd/myapp

# 运行阶段(瘦身)
FROM scratch # 空镜像
COPY --from=builder /app/myapp /myapp
ENTRYPOINT ["/myapp"]

用途:Go、Rust、Java(只取 jar)、前端打包(只取静态文件)等。


2. 分离测试/校验阶段——构建时的质量门

在构建前先做 lint、单元测试、静态分析,失败则终止,合格才进入下一阶段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
FROM node:20 AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

FROM deps AS test
COPY . .
RUN npm run lint && npm test

FROM deps AS build
COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html

用途:CI 中确保构建物通过质量校验再打包。


3. 多目标(target)构建:按需构建不同产物

同一个 Dockerfile 定义多个可选目标,构建时通过 --target 选择。

1
2
3
4
5
6
7
8
9
10
11
12
13
FROM python:3.12 AS base
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt

FROM base AS dev
RUN pip install debugpy
COPY . .

FROM base AS prod
ENV FLASK_ENV=production
COPY . .
CMD ["python", "app.py"]

构建开发镜像:
docker build --target dev -t myapp:dev .
构建生产镜像:
docker build --target prod -t myapp:prod .


4. 提取中间构件(artifact extraction)

只取某个阶段的某个文件、资源,避免冗余。

1
2
3
4
5
6
7
8
9
FROM maven:3.9-openjdk-21 AS build
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN mvn package -DskipTests

FROM openjdk:21-jdk-slim
COPY --from=build /app/target/app.jar /app/app.jar
ENTRYPOINT ["java", "-jar", "/app/app.jar"]

5. 构建多平台镜像:共享构建逻辑但在不同基础上打包

利用 ARG/变量控制基础镜像(结合 CI 里的 --platform )。

1
2
3
4
5
6
ARG BASE=alpine:3.19
FROM ${BASE} AS base
RUN echo "这可以按 platform 或场景切换"

FROM base AS final
CMD ["sh", "-c", "echo hello"]

构建时替换:
docker build --build-arg BASE=ubuntu:24.04 -t custom .


6. 凭证/密钥隔离 —— 构建时用到但不落入最终镜像

在构建阶段使用私密(如下载私有依赖),最终镜像不包含这些凭证。

1
2
3
4
5
6
7
8
9
10
11
FROM node:20 AS builder
WORKDIR /app
# 构建时用到私有 npm token
ARG NPM_TOKEN
RUN echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc
COPY . .
RUN npm ci && npm run build
RUN rm -f .npmrc

FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html

注意:不要将 .npmrc 等在最终镜像中残留。


7. 复用缓存层+共享依赖(提高构建速度)

多个目标共享中间层,比如依赖安装分离成公共 stage。

1
2
3
4
5
6
7
8
9
FROM python:3.12-slim AS deps
WORKDIR /app
COPY requirements.txt .
RUN pip install --user -r requirements.txt

FROM deps AS prod
COPY . .
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "server.py"]

这样依赖层可以被多个派生阶段复用,避免重复安装。


8. 安全/扫描阶段:先扫描构建产物再打包

结合静态安全扫描工具(如 trivy、gosec)作为单独阶段,在构建失败前阻断不合格产物。

1
2
3
4
5
6
7
8
9
10
11
12
13
FROM golang:1.21 AS build
WORKDIR /app
COPY . .
RUN go build -o myapp ./cmd/myapp

FROM aquasec/trivy:latest AS scan
COPY --from=build /app/myapp /myapp
# 假设 scan 脚本对二进制做检查并非真正运行(伪代码)
RUN trivy fs /myapp

FROM scratch AS final
COPY --from=build /app/myapp /myapp
ENTRYPOINT ["/myapp"]

最佳实践小贴士

  • 明确命名各阶段:AS builder, AS test, AS final 让可读性强。
  • 只把运行时必要文件拷贝到最终阶段,避免泄露源代码或构建工具链。
  • 尽量把缓存稳定的层(如依赖安装)放前面,减少每次变更带来的重构。
  • 利用 --target 在 CI 里做分支化构建(dev/test/prod)。
  • 对于私密数据,永远不要在最终镜像保留构建用的凭证。