在使用docker build命令的时候,有时候需要根据不同的规则指定不同的构建脚本,比如 dockerfile分为prod或test,或者将 变量传到dockerfile中根据不同的条件执行不同的构建命令; 还比如在不同的打包环境下可能构建出的产物目录不一样,有的是dist或dist/app1或dist/app2, 那么有哪些方式可以灵活的实现这些差异化构建呢?
🌙 几种实现差异化构建的主流方式
🌙 方式一:使用不同的 Dockerfile 文件 (-f
参数)
这是最直接、最简单的方式。你可以为不同的环境创建不同的Dockerfile。
Dockerfile.prod
(生产环境)Dockerfile.dev
(开发环境)Dockerfile.test
(测试环境)
工作原理:
在 docker build
命令中使用 -f
或 --file
参数来明确指定使用哪个构建脚本。
示例:
Dockerfile.prod
:
# === 生产环境构建脚本 ===
# Stage 1: 构建前端应用
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
# 生产环境构建命令
RUN npm run build --prod
# Stage 2: 部署到 Nginx
FROM nginx:stable-alpine
# 从构建阶段拷贝产物,假设产物在 dist 目录
COPY /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Dockerfile.test
:
# === 测试环境构建脚本 ===
# Stage 1: 构建前端应用
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
# 测试环境构建命令,可能包含测试工具
RUN npm run build:test
# Stage 2: 部署到 Nginx
FROM nginx:stable-alpine
# 从构建阶段拷贝产物,假设产物在 test-dist 目录
COPY /app/test-dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
构建命令:
# 构建生产镜像
docker build -t my-app:prod -f Dockerfile.prod .
# 构建测试镜像
docker build -t my-app:test -f Dockerfile.test .
2
3
4
5
- 优点:
- 完全隔离:每个环境的构建逻辑完全独立,互不干扰。
- 清晰易懂:对于差异巨大的构建流程,这种方式最直观。
- 缺点:
- 代码冗余:如果两个文件大部分内容都相同(比如基础镜像、依赖安装步骤),维护起来很麻烦。修改一个公共部分,需要同步修改所有文件,容易出错。
- 适用场景:当不同环境的构建逻辑差异非常大时(例如,基础镜像都不同,或者构建流程完全不一样)。
🌙 方式二:使用构建参数 (ARG
和 --build-arg
)
这是最灵活、最推荐的方式,尤其适合你提到的场景。它允许你将变量从 docker build
命令传递到 Dockerfile 内部,从而实现条件判断。
工作原理:
- 在 Dockerfile 中使用
ARG
指令定义一个构建时变量。 - 在
docker build
命令中使用--build-arg
来传递这个变量的值。 - 在 Dockerfile 的
RUN
,COPY
等指令中使用这个变量。
示例1:根据变量执行不同的构建命令
Dockerfile
(单一文件):
FROM node:18-alpine AS builder
WORKDIR /app
# 定义一个构建环境的参数,默认值为 "prod"
ARG BUILD_ENV=prod
COPY package*.json ./
RUN npm install
COPY . .
# 根据 BUILD_ENV 的值执行不同的命令
RUN if [ "$BUILD_ENV" = "prod" ]; then \
echo ">>> Running production build" && npm run build; \
else \
echo ">>> Running test build" && npm run build:test; \
fi
# ...后续阶段...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
构建命令:
# 构建生产镜像 (使用默认 ARG 值)
docker build -t my-app:prod .
# 构建测试镜像 (传递不同的 ARG 值)
docker build --build-arg BUILD_ENV=test -t my-app:test .
2
3
4
5
示例2:处理不同的产物目录 (dist
, dist/app1
, dist/app2
)
Dockerfile
(单一文件):
# Stage 1: 构建
FROM node:18-alpine AS builder
WORKDIR /app
# ... (npm install, etc.)
COPY . .
RUN npm run build # 假设这个命令会生成 dist/app1 或 dist/app2
# Stage 2: 部署
FROM nginx:stable-alpine
# 定义一个参数来指定资源路径,并给一个安全的默认值
ARG ASSET_PATH=dist
# 在 COPY 命令中使用这个变量
# 注意:$ASSET_PATH 后面必须有斜杠,表示拷贝目录内容
COPY /app/${ASSET_PATH}/ /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
构建命令:
# 默认构建 (产物在 dist 目录)
docker build -t my-app:latest .
# 构建 app1 (产物在 dist/app1 目录)
docker build --build-arg ASSET_PATH=dist/app1 -t my-app:app1 .
# 构建 app2 (产物在 dist/app2 目录)
docker build --build-arg ASSET_PATH=dist/app2 -t my-app:app2 .
2
3
4
5
6
7
8
- 优点:
- 单一文件:维护成本低,所有逻辑集中在一个地方。
- 高度灵活:非常适合CI/CD流水线,只需改变传入的参数即可。
- 避免冗余:所有环境共享公共的构建步骤。
- 缺点:
- Dockerfile 内部可能会出现一些
if/else
逻辑,稍微增加了一点复杂度。
- Dockerfile 内部可能会出现一些
- 适用场景:绝大多数需要差异化构建的场景,是事实上的标准做法。
🌙 方式三:利用多阶段构建的 target
多阶段构建不仅可以减小镜像体积,还可以用来分离不同的构建目标。
工作原理:
你可以定义多个构建阶段,比如 builder
, tester
, production
。然后在 docker build
时使用 --target
参数来指定构建到哪一个阶段为止。
示例:分离测试和构建
Dockerfile
:
# ---- 基础构建阶段 ----
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
# ---- 测试阶段 ----
FROM builder AS tester
# 运行测试,如果测试失败,此阶段会失败
RUN npm test
# ---- 生产镜像阶段 ----
FROM builder AS release-builder
# 运行生产构建
RUN npm run build
# ---- 最终的生产镜像 ----
FROM nginx:stable-alpine AS production
COPY /app/dist/ /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
构建命令:
# 1. 只运行测试,不生成最终镜像
# 如果 npm test 失败,整个构建就会失败,非常适合在 CI 中做代码检查
docker build --target tester -t my-app:test-runner .
# 2. 构建最终的生产镜像
# 这会自动执行依赖的阶段 (builder -> release-builder -> production)
docker build --target production -t my-app:latest .
2
3
4
5
6
7
- 优点:
- 逻辑清晰:将测试、构建、部署等不同关注点分离到不同的阶段。
- CI/CD 友好:可以先构建
tester
阶段来验证代码,通过后再构建production
阶段。
- 缺点:
- 它主要解决的是构建流程的分离,对于条件化拷贝文件等细粒度控制,仍需结合方式二 (
ARG
) 来实现。
- 它主要解决的是构建流程的分离,对于条件化拷贝文件等细粒度控制,仍需结合方式二 (
- 适用场景:希望在同一个 Dockerfile 中定义单元测试、集成测试、生产构建等多个独立目标。
🌙 最佳实践与总结
推荐组合使用 方式二 (ARG)
和多阶段构建。
乃是当前最现代化、最灵活、最易于维护的方案。
方案 | 优点 | 缺点 | 最佳适用场景 |
---|---|---|---|
1. 多文件 (-f ) | 完全隔离,逻辑清晰 | 代码冗余,维护困难 | 环境差异巨大,几乎无公共部分时 |
2. 构建参数 (ARG ) | 灵活,无冗余,CI/CD友好 | 需在 RUN 中写少量shell逻辑 | 95%的差异化构建场景 |
3. 多阶段目标 (--target ) | 流程分离,适合 CI 门禁 | 不直接解决条件判断问题 | 定义测试、构建等不同构建目标 |
🌙 最终建议方案 (综合示例)
在一个名为 Dockerfile
的文件中,结合多阶段构建和 ARG
参数:
# =========================================================
# Stage 1: Builder - 通用的构建环境
# =========================================================
FROM node:18-alpine AS builder
WORKDIR /app
# --- ARG 定义区 ---
# 定义构建产物的路径,默认是 'dist'
ARG ASSET_PATH=dist
# 定义构建脚本命令,默认是 'build'
ARG BUILD_SCRIPT=build
# 打印出接收到的参数,便于调试
RUN echo ">>> Building with ASSET_PATH=${ASSET_PATH} and SCRIPT=${BUILD_SCRIPT}"
COPY package*.json ./
RUN npm install
COPY . .
# 执行由参数指定的构建脚本
RUN npm run ${BUILD_SCRIPT}
# =========================================================
# Stage 2: Production - 最终的生产镜像
# =========================================================
FROM nginx:stable-alpine
# ARG 必须在每个阶段重新声明才能使用
ARG ASSET_PATH=dist
# 从 builder 阶段拷贝由 ASSET_PATH 指定的目录内容
COPY /app/${ASSET_PATH}/ /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
这样,你的 CI/CD 流水线就可以极其灵活地构建:
# 场景A: 标准生产构建 (产物在 dist)
docker build -t my-app:1.0.0 .
# 场景B: 为项目 app1 构建 (产物在 project/app1, 使用 build:app1 脚本)
docker build \
--build-arg ASSET_PATH=project/app1 \
--build-arg BUILD_SCRIPT=build:app1 \
-t my-app-app1:1.0.0 .
# 场景C: 为项目 app2 构建 (产物在 project/app2, 使用 build:app2 脚本)
docker build \
--build-arg ASSET_PATH=project/app2 \
--build-arg BUILD_SCRIPT=build:app2 \
-t my-app-app2:1.0.0 .
2
3
4
5
6
7
8
9
10
11
12
13
14
这个方案将 Dockerfile 的维护成本降到最低,同时将灵活性发挥到了极致,完全符合自动化运维对健壮性和可维护性的要求。