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 .
只取某个阶段的某个文件、资源,避免冗余。
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
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
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)。
- 对于私密数据,永远不要在最终镜像保留构建用的凭证。