DockerFile-ARG值为空的问题

DockerFile-ARG值为空的问题

文章翻译自: https://qmacro.org/blog/posts/2024/05/13/using-arg-in-a-dockerfile-beware-the-gotcha/

先说结论:

“在FROM之前的ARG声明是构建阶段之外的,因此它不能在FROM之后的任何指令中使用。” 换句话说,用ARG声明的变量看起来像是脚本中的变量,这些变量通常也在开始时声明,然后在脚本中使用。

ARG值为空的问题

作者花费了不少时间来理解为什么他自定义构建的CAP Node.js容器镜像的CAP版本并不是他指定的版本,无论是在Dockerfile中通过ARG指令隐式声明的默认值,还是通过命令行的--build-arg选项显式指定的值。

为了说明问题,作者提供了一个简化版的Dockerfile:

# syntax=docker/dockerfile:1
ARG DEBVER="10"
ARG CAPVER="7.8"
FROM debian:${DEBVER}
RUN printf "DEB=${DEBVER}\nCAP=${CAPVER}\n" > /tmp/log

这里声明的第一个变量DEBVER是一个常见的用例,允许使用不同版本的基础镜像,这里的例子是从不同版本的Debian发行版开始,其默认版本是10。

第二个变量CAPVER也是类似的,作者在Dockerfile后面的构建指令中使用它来指定想要安装的CAP的特定版本。实际的指令看起来像这样:RUN npm install -g @sap/cds-dk@{CAPVER}

基于这个简化的Dockerfile构建镜像,没有使用--build-arg显式指定任何值:

docker build -t argtest .

作者可以成功确认从这个镜像创建的容器中的Debian版本是10:

docker run --rm argtest grep VERSION_ID /etc/os-release
VERSION_ID="10"

但是/tmp/log的内容呢?

docker run --rm argtest cat /tmp/log
DEB=
CAP=

使用--build-arg选项又如何呢?

docker build \
  --build-arg="DEBVER=11" \
  --build-arg="CAPVER=7.9" \
  -t argtest .

构建成功完成,现在可以看到容器基于Debian 11:

docker run --rm argtest grep VERSION_ID /etc/os-release
VERSION_ID="11"

但是DEBVERCAPVER/tmp/log中的空值问题仍然存在。

不仅在RUN指令中引用CAPVER的值是空的,而且,这是迄今为止最神秘的事情,尽管DEBVERFROM指令中确实被识别并设置为11,用于Debian发行版,但在FROM指令之后的RUN指令中引用它时却是空的。

ARGFROM之间的关系微妙之处

原因是ARGFROM之间相当微妙的关系,这个解释简短而有点隐藏在主要的Dockerfile参考文档中。 作者在直接查找ARG的参考时错过了这一点,因为它没有被提及,只有在FROM的参考文档的末尾才解释,而FROM的参考文档在页面上更靠前。

关键部分在这里:理解ARGFROM如何反应,并包括这句话:

“在FROM之前的ARG声明是构建阶段之外的,因此它不能在FROM之后的任何指令中使用。” 换句话说,用ARG声明的变量看起来像是脚本中的变量,这些变量通常也在开始时声明,然后在脚本中使用。

但它们不是。

解决方案

必须对上面的Dockerfile进行修改,使其看起来像这样:

# syntax=docker/dockerfile:1
ARG DEBVER="10"
FROM debian:${DEBVER}
ARG DEBVER
ARG CAPVER="7.8"
RUN printf "DEB=${DEBVER}\nCAP=${CAPVER}\n" > /tmp/log

CAPVERARG指令移动到FROM指令之后,使其具有生命力和有效性。

DEBVERARG指令必须保持在原来的位置(因为它在FROM指令的详细信息中被引用),但如果需要在FROM指令之后引用它,它必须再次被引用 - 因此是ARG DEBVER这一行。

使用这个新版本的Dockerfile构建的容器镜像,如果没有指定--build-arg选项,我们可以看到DEBVERCAPVER的值在FROM指令之后是可用的:

docker run --rm argtest cat /tmp/log
DEB=10
CAP=7.8

当然,如果我们在命令行上设置构建参数的值,这也适用,测试使用相同的docker build --build-arg ...命令构建的容器:

docker run --rm argtest cat /tmp/log
DEB=11
CAP=7.9

以及

docker run --rm argtest grep VERSION_ID /etc/os-release
VERSION_ID="11"
目录