1. 概述

Docker 提供了多阶段构建(Multi-stage builds)功能,允许开发者定义多个构建阶段,每个阶段都可以拥有独立的指令集。这有助于优化最终镜像的体积。在多阶段构建中,一个常见的挑战是在不同阶段之间传递变量。

跨阶段复制变量非常有用,它允许我们在后续阶段中复用前面阶段中定义的值。

在本教程中,我们将探讨三种在多阶段 Docker 构建中复制变量的方法:使用 ARG、使用 ENV 和使用文件。我们将了解每种方法的工作原理,并通过示例说明其使用方式。


2. 使用 ARG

在多阶段 Docker 构建中,使用 ARG 是在不同阶段之间复制变量的最简单方式。ARG 变量可以在构建过程中使用,并且可以通过 docker build 命令以构建参数的形式传入。

来看一个使用 ARG 的 Dockerfile 示例:

# 阶段 1
FROM alpine AS stage1
ARG MY_VAR
RUN echo MY_VAR value is $MY_VAR

# 阶段 2
FROM alpine AS stage2
ARG MY_VAR
RUN echo MY_VAR is still set to: $MY_VAR

在上面的例子中,我们在两个阶段中都使用 ARG 定义了一个名为 MY_VAR 的变量,并通过 RUN 指令在每个阶段中打印其值。

构建镜像并传入变量值的命令如下:

$ docker build --build-arg MY_VAR=somevalue -t my_image .

输出结果如下:

$ docker build --build-arg MY_VAR=somevalue -t my_image .
Sending build context to Docker daemon  2.048kB
Step 1/6 : FROM alpine AS stage1
 ---> b2aa39c304c2
Step 2/6 : ARG MY_VAR
 ---> Running in 38b3ef3093c3
Removing intermediate container 38b3ef3093c3
 ---> 5619deec98e2
Step 3/6 : RUN echo MY_VAR value is $MY_VAR
 ---> Running in 1bf3db328de6
MY_VAR value is somevalue
Removing intermediate container 1bf3db328de6
 ---> 5e9f9bc1255a
Step 4/6 : FROM alpine AS stage2
 ---> b2aa39c304c2
Step 5/6 : ARG MY_VAR
 ---> Running in 19d18d18f055
Removing intermediate container 19d18d18f055
 ---> 842a58a44c8e
Step 6/6 : RUN echo MY_VAR is still set to: $MY_VAR
 ---> Running in 88ba4ea6188b
MY_VAR is still set to: somevalue
Removing intermediate container 88ba4ea6188b
 ---> 6b912736db48
Successfully built 6b912736db48
Successfully tagged my_image:latest

从输出中可以看出,ARG 定义的 MY_VAR 在两个阶段中都保留了其值。

优点:适用于构建时传参,不污染最终镜像
缺点:不能持久化到最终镜像中


3. 使用 ENV

另一种在多阶段 Docker 构建中传递变量的方式是使用 ENVENV 定义的变量在构建过程中可用,并且会持久化到最终镜像中。

来看一个使用 ENV 的 Dockerfile 示例:

FROM alpine:latest as base
ENV MY_ENV="my_env"

FROM base
RUN echo ${MY_ENV}

在这个例子中,我们在第一个阶段定义了一个环境变量 MY_ENV,它在后续阶段中仍然可用。

执行构建命令如下:

$ docker build --no-cache -t my_image .

输出如下:

Step 1/4 : FROM alpine:latest as base
 ---> b2aa39c304c2
Step 2/4 : ENV MY_ENV="my_env"
 ---> Running in 7ffd89e261a2
Removing intermediate container 7ffd89e261a2
 ---> 17e87c9864c3
Step 3/4 : FROM base
 ---> 17e87c9864c3
Step 4/4 : RUN echo ${MY_ENV}
 ---> Running in 337470d2041d
my_env
Removing intermediate container 337470d2041d
 ---> 8e6abc31f316
Successfully built 8e6abc31f316
Successfully tagged my_image:latest

从 Step 4 的输出可以看到,MY_ENV 的值成功从第一个阶段传递到了第二个阶段。

⚠️ 注意:ENV 变量是持久化的,适用于需要在最终镜像中访问的变量。

优点:变量可持久化到最终镜像中
缺点:无法通过命令行传参


4. 使用文件

当需要在多个阶段之间共享大量变量时,使用文件是一个非常实用的方法。这种方式通过在前一个阶段将变量写入文件,在后续阶段读取该文件来实现变量传递。

来看一个使用文件传递变量的 Dockerfile 示例:

# 阶段 1 - Build
FROM alpine AS build

# 将变量值写入文件
RUN echo "hello" > /tmp/myvar

# 阶段 2 - Run
FROM alpine AS run

# 从 build 阶段复制文件
COPY --from=build /tmp/myvar /tmp/myvar

# 读取文件内容并赋值给变量
RUN MY_VAR=$(cat /tmp/myvar) && echo "Running the app with MY_VAR=$MY_VAR"

构建命令如下:

$ docker build --no-cache -t my_image .

输出如下:

Step 1/6 : FROM alpine AS build
 ---> b2aa39c304c2
Step 2/6 : RUN echo "hello" > /tmp/myvar
 ---> Running in db7cbe1a060f
Removing intermediate container db7cbe1a060f
 ---> 3c64d471fe3a
Step 3/6 : RUN echo "Building the app..."
 ---> Running in 08c4d8024c53
Building the app...
Removing intermediate container 08c4d8024c53
 ---> 4cc6f20768e5
Step 4/6 : FROM alpine AS run
 ---> b2aa39c304c2
Step 5/6 : COPY --from=build /tmp/myvar /tmp/myvar
 ---> 3dc259424cb6
Step 6/6 : RUN MY_VAR=$(cat /tmp/myvar) && echo "Running the app with MY_VAR=$MY_VAR"
 ---> Running in 961f02320a71
Running the app with MY_VAR=hello
Removing intermediate container 961f02320a71
 ---> 1a8c5349f42b
Successfully built 1a8c5349f42b
Successfully tagged my_image:latest

我们还可以验证最终镜像中该文件的内容:

$ docker run --rm my_image /bin/sh -c "cat /tmp/myvar"
hello

优点:适合传递多个变量,支持在最终镜像中访问
缺点:需要手动处理文件读写,略微繁琐


5. 总结

在本教程中,我们介绍了三种在多阶段 Docker 构建中复制变量的方法:

方法 是否持久化 是否可传参 适用场景
ARG ❌ 否 ✅ 是 构建时传参,不污染镜像
ENV ✅ 是 ❌ 否 需要在最终镜像中使用的变量
文件 ✅ 是 ✅ 是 多变量传递或需持久化

根据不同的使用场景选择合适的方式,可以更高效地管理多阶段构建流程。希望本文能帮助你避免在实际项目中“踩坑”!


原始标题:Copy Variables Between Stages in a Multi Stage Docker Build