Moby: 将ENV和ARG设置保留到多阶段构建的所有后续阶段

创建于 2018-06-26  ·  20评论  ·  资料来源: moby/moby

描述

Afaik,没有办法在各个阶段之间共享变量(如果我错了,请纠正我)。 要从一个构建阶段共享到下一个构建阶段,唯一的选择是将一个阶段的目录中的COPY文件复制到当前阶段。 我们可以使用构建时变量的转储来构建类似JSON文件的东西,但是我认为这个问题太过频繁了,它将使我们的多阶段构建充满各种疯狂的JSON解析问题。 我们可以自动化吗?

重现此问题的步骤:

FROM alpine:latest as base1
ARG v1=test
ENV v1=$v1
RUN echo ${v1}

FROM alpine:latest as base2
RUN echo ${v1}

描述您收到的结果:

docker build --no-cache  .
Step 4/6 : RUN echo ${v1}
---> Running in b60a3079864b
test

...

Step 5/6 : FROM alpine:latest as base2
---> 3fd9065eaf02
Step 6/6 : RUN echo ${v1}
---> Running in 1147977afd60

描述您期望的结果:

docker build --no-cache --multistage-share-env --multistage-share-arg .
Step 4/6 : RUN echo ${v1}
---> Running in b60a3079864b
test

...

Step 5/6 : FROM alpine:latest as base2
---> 3fd9065eaf02
Step 6/6 : RUN echo ${v1}
---> Running in 1147977afd60
test

或者也许我们可以使用诸如名称空间之类的方法来插值${base1.v1}

arebuilder kinquestion

最有用的评论

正确的Dockerfile指令(包括ENV vars和ARG在每个构建阶段都具有作用域,并且不会在下一阶段保留; 这是设计使然。

您可以_can_,设置一个_global_ ARG (在第一个构建阶段之前设置),并在每个构建阶段中使用该值。

ARG version_default=v1

FROM alpine:latest as base1
ARG version_default
ENV version=$version_default
RUN echo ${version}
RUN echo ${version_default}

FROM alpine:latest as base2
ARG version_default
RUN echo ${version_default}

在未设置--build-arg情况下进行构建;

docker build --no-cache -<<'EOF'
ARG version_default=v1

FROM alpine:latest as base1
ARG version_default
ENV version=$version_default
RUN echo ${version}
RUN echo ${version_default}

FROM alpine:latest as base2
ARG version_default
RUN echo ${version_default}
EOF

这将产生:

Sending build context to Docker daemon  2.048kB
Step 1/9 : ARG version_default=v1
Step 2/9 : FROM alpine:latest as base1
 ---> 3fd9065eaf02
Step 3/9 : ARG version_default
 ---> Running in 702c05d6f294
Removing intermediate container 702c05d6f294
 ---> 1b2cac6e7585
Step 4/9 : ENV version=$version_default
 ---> Running in 6fb73bc8cdb9
Removing intermediate container 6fb73bc8cdb9
 ---> 656d82ccb6d7
Step 5/9 : RUN echo ${version}
 ---> Running in 403c720d0031
v1
Removing intermediate container 403c720d0031
 ---> d6071c5bd329
Step 6/9 : RUN echo ${version_default}
 ---> Running in d5c76d7d3aaa
v1
Removing intermediate container d5c76d7d3aaa
 ---> 554df1d8584b
Step 7/9 : FROM alpine:latest as base2
 ---> 3fd9065eaf02
Step 8/9 : ARG version_default
 ---> Running in 92400e85c722
Removing intermediate container 92400e85c722
 ---> 5f0cb12f4448
Step 9/9 : RUN echo ${version_default}
 ---> Running in f38802f0d690
v1
Removing intermediate container f38802f0d690
 ---> 4b8caab7870a
Successfully built 4b8caab7870a

还有一个--build-arg ;

docker build --no-cache --build-arg version_default=v2 -<<'EOF'
ARG version_default=v1

FROM alpine:latest as base1
ARG version_default
ENV version=$version_default
RUN echo ${version}
RUN echo ${version_default}

FROM alpine:latest as base2
ARG version_default
RUN echo ${version_default}
EOF
Sending build context to Docker daemon  2.048kB
Step 1/9 : ARG version_default=v1
Step 2/9 : FROM alpine:latest as base1
 ---> 3fd9065eaf02
Step 3/9 : ARG version_default
 ---> Running in 7f5dd5885859
Removing intermediate container 7f5dd5885859
 ---> 482ffb014095
Step 4/9 : ENV version=$version_default
 ---> Running in b6c6e9aa3489
Removing intermediate container b6c6e9aa3489
 ---> 83f1c0b82986
Step 5/9 : RUN echo ${version}
 ---> Running in 0805ec04fd20
v2
Removing intermediate container 0805ec04fd20
 ---> ef39d4bd6306
Step 6/9 : RUN echo ${version_default}
 ---> Running in f8747a5bfeeb
v2
Removing intermediate container f8747a5bfeeb
 ---> 72d497d25306
Step 7/9 : FROM alpine:latest as base2
 ---> 3fd9065eaf02
Step 8/9 : ARG version_default
 ---> Running in 57aa2e097787
Removing intermediate container 57aa2e097787
 ---> 45e167d234ce
Step 9/9 : RUN echo ${version_default}
 ---> Running in 8615cd6f6ab6
v2
Removing intermediate container 8615cd6f6ab6
 ---> 1674ad8d3b88
Successfully built 1674ad8d3b88

所有20条评论

正确的Dockerfile指令(包括ENV vars和ARG在每个构建阶段都具有作用域,并且不会在下一阶段保留; 这是设计使然。

您可以_can_,设置一个_global_ ARG (在第一个构建阶段之前设置),并在每个构建阶段中使用该值。

ARG version_default=v1

FROM alpine:latest as base1
ARG version_default
ENV version=$version_default
RUN echo ${version}
RUN echo ${version_default}

FROM alpine:latest as base2
ARG version_default
RUN echo ${version_default}

在未设置--build-arg情况下进行构建;

docker build --no-cache -<<'EOF'
ARG version_default=v1

FROM alpine:latest as base1
ARG version_default
ENV version=$version_default
RUN echo ${version}
RUN echo ${version_default}

FROM alpine:latest as base2
ARG version_default
RUN echo ${version_default}
EOF

这将产生:

Sending build context to Docker daemon  2.048kB
Step 1/9 : ARG version_default=v1
Step 2/9 : FROM alpine:latest as base1
 ---> 3fd9065eaf02
Step 3/9 : ARG version_default
 ---> Running in 702c05d6f294
Removing intermediate container 702c05d6f294
 ---> 1b2cac6e7585
Step 4/9 : ENV version=$version_default
 ---> Running in 6fb73bc8cdb9
Removing intermediate container 6fb73bc8cdb9
 ---> 656d82ccb6d7
Step 5/9 : RUN echo ${version}
 ---> Running in 403c720d0031
v1
Removing intermediate container 403c720d0031
 ---> d6071c5bd329
Step 6/9 : RUN echo ${version_default}
 ---> Running in d5c76d7d3aaa
v1
Removing intermediate container d5c76d7d3aaa
 ---> 554df1d8584b
Step 7/9 : FROM alpine:latest as base2
 ---> 3fd9065eaf02
Step 8/9 : ARG version_default
 ---> Running in 92400e85c722
Removing intermediate container 92400e85c722
 ---> 5f0cb12f4448
Step 9/9 : RUN echo ${version_default}
 ---> Running in f38802f0d690
v1
Removing intermediate container f38802f0d690
 ---> 4b8caab7870a
Successfully built 4b8caab7870a

还有一个--build-arg ;

docker build --no-cache --build-arg version_default=v2 -<<'EOF'
ARG version_default=v1

FROM alpine:latest as base1
ARG version_default
ENV version=$version_default
RUN echo ${version}
RUN echo ${version_default}

FROM alpine:latest as base2
ARG version_default
RUN echo ${version_default}
EOF
Sending build context to Docker daemon  2.048kB
Step 1/9 : ARG version_default=v1
Step 2/9 : FROM alpine:latest as base1
 ---> 3fd9065eaf02
Step 3/9 : ARG version_default
 ---> Running in 7f5dd5885859
Removing intermediate container 7f5dd5885859
 ---> 482ffb014095
Step 4/9 : ENV version=$version_default
 ---> Running in b6c6e9aa3489
Removing intermediate container b6c6e9aa3489
 ---> 83f1c0b82986
Step 5/9 : RUN echo ${version}
 ---> Running in 0805ec04fd20
v2
Removing intermediate container 0805ec04fd20
 ---> ef39d4bd6306
Step 6/9 : RUN echo ${version_default}
 ---> Running in f8747a5bfeeb
v2
Removing intermediate container f8747a5bfeeb
 ---> 72d497d25306
Step 7/9 : FROM alpine:latest as base2
 ---> 3fd9065eaf02
Step 8/9 : ARG version_default
 ---> Running in 57aa2e097787
Removing intermediate container 57aa2e097787
 ---> 45e167d234ce
Step 9/9 : RUN echo ${version_default}
 ---> Running in 8615cd6f6ab6
v2
Removing intermediate container 8615cd6f6ab6
 ---> 1674ad8d3b88
Successfully built 1674ad8d3b88

另一种方法是将基础容器用于多个阶段:

FROM alpine:latest as base
ARG version_default
ENV version=$version_default

FROM base
RUN echo ${version}

FROM base
RUN echo ${version}
docker build --build-arg=version_default=123 --no-cache .   
Sending build context to Docker daemon  92.67kB
Step 1/7 : FROM alpine:latest as base
 ---> 3fd9065eaf02
Step 2/7 : ARG version_default
 ---> Running in a1ebfdf79f07
Removing intermediate container a1ebfdf79f07
 ---> 3e78800ed9ea
Step 3/7 : ENV version=$version_default
 ---> Running in 105d94baac3f
Removing intermediate container 105d94baac3f
 ---> a14276ddc77b
Step 4/7 : FROM base
 ---> a14276ddc77b
Step 5/7 : RUN echo ${version}
 ---> Running in d92f9b48a6cc
123
Removing intermediate container d92f9b48a6cc
 ---> 6505fe2a14bb
Step 6/7 : FROM base
 ---> a14276ddc77b
Step 7/7 : RUN echo ${version}
 ---> Running in 1b748eea4ef3
123
Removing intermediate container 1b748eea4ef3
 ---> f3311d3ad27e
Successfully built f3311d3ad27e
Time: 0h:00m:04s

让我结束这个问题,因为这是设计使然,但希望以上示例对您有进一步的帮助; 也可以继续对话

如果这是设计使然,那么设计是错误的。 如果没有在阶段之间共享变量的简洁方法,则无法进行DRY。 在不可避免的情况下,很多阶段都需要访问相同的变量。 复制定义容易出错,复制样板也很容易使共享定义进入每个阶段。

“基础容器”方法严重限制了表达力,因为它固定了每个阶段的基础容器,而在某些有效的用例中,中间阶段每个都需要使用提供该阶段所需工具的最小基础图像。

@thaJeztah
可以重新考虑这种设计吗?

我有11个基本映像,这些映像仅在内部可用,主要用于我们的各种构建。 我正在尝试制作一个基本的“ FROM暂存”映像,该11个映像将用作多阶段构建的一部分,因为在所有11个映像中,某些逻辑是相同的,其中包括环境变量。 因此,我需要在每个图像中设置环境变量,并希望在我的基本图像中设置环境变量,这样就不需要在其他所有图像中复制相同的逻辑。

@ DMaxfield-BDS范围ARG不会改变,但是您可以考虑使用一个单独的构建目标来创建“ FROM临时”基础映像; 将其推送到注册表,并将if用于其他图像;

FROM scratch AS scratch-base
ENV foo=bar
ENV bar=baz

构建它,并将其推送到您的(内部)注册表中,这11个基本映像可以将其用作基本映像

FROM scratch-base
RUN your stuff

还有一个建议是使用EXPORT/IMPORT ,这可能适合其他一些用例。 https://github.com/moby/moby/issues/32100

@thaJeztah

感谢您的答复。 我没有指出的一件事是,这其他11张图片都具有不同的基本图片(python,npm,postgres,openjdk等)。 因此,我要做的就是将所有通用的设置/准备工作放到一个基本映像中,包括设置我的公司应用程序使用的所需环境变量,如下所示:

FROM scratch AS scratch-base
ARG JAR_DIR='/Input/jars/'
ENV JAR_DOWNLOAD_DIR=$JAR_DIR

推送到我的内部注册表。 然后,其他11张图片之一的示例将执行以下操作:

FROM scratch-base AS scratch-base
FROM openjdk:8-jdk
COPY --from=scratch-base $JAR_DOWNLOAD_DIR .
RUN <stuff that also uses the JAR_DOWNLOAD_DIR environment variable>

如果我能够使用在第一张图像中设置的环境变量,则不必在其他11张图像中都配置这些相同的设置。 这为我提供了一个配置点,并实现了更好的自动化。

我应该怎么做这样的事情?

RUN COMMIT_HASH = $(git rev-parse --short HEAD)

如何允许在随后的RUN步骤中使用COMMIT_HASH变量? 我被迫定义变量并一步一步使用它吗?

我认为这种设计非常有限。 也许可以:

RUN COMMIT_HASH = $(git rev-parse --short HEAD)AS提交哈希
ARG --from = commit-hash COMMIT_HASH
RUN echo $ {COMMIT_HASH}

@ dnk8n这是我要解决的方法:

在构建阶段,我要做的是:

RUN git rev-parse HEAD > commit_hash

然后在另一阶段,我复制带有数据&的文件,并在运行使用它的应用程序之前设置Environment变量:

COPY --from=builder /<build_folder>/commit_hash /<other_stage_folder>/commit_hash

CMD export COMMIT_HASH=$(cat /<other_stage_folder>/commit_hash); java -jar myApp.jar

您应该能够执行类似的操作来设置ARG。

至少我们应该能够执行类似的操作,将BAR变量从另一个阶段/映像复制到当前构建范围中。

ENV --from=stage1 FOO=$BAR

当考虑使用外部图像作为舞台时,这一点尤为重要,因为环境变量中可能存在重要的元数据。

ENV --from=nginx:latest FOO=$BAR

我正在通过使用以下模式在每个阶段将环境保存到文件来解决:

FROM base1:version1 AS BUILDER1
WORKDIR /build
RUN env | sort > env.BUILDER1

FROM base2:version2 AS BUILDER2
WORKDIR /build
RUN env | sort > env.BUILDER2

FROM finalbase:version AS FINAL
WORKDIR /build
ENV LANG=C.UTF-8
COPY --from=BUILDER1 /build/env.BUILDER1 .
COPY --from=BUILDER2 /build/env.BUILDER2 .
# Use ". ./env.BUILDER" instead of "source" if /bin/sh is true-POSIX
RUN set -eux ; \
        source ./env.BUILDER1 ; \
        source ./env.BUILDER2 ; \
        env | sort

我不确定如何在运行docker inspect时最终将其“保存”到“ docker”中,但是例如可以将其放入/ etc / profile中。

image

看到最后的评论是如此新鲜,仅让我瞥见了多少开发人员对dockerfile背后的设计决策提出了质疑。 显然“ DRY”不在掌握的技能之列。 如果您想尝试,添加另一层并开始模板化dockerfiles ...

老实说,我对此感到非常惊讶……“ global arg”似乎不适用于我。 如果我将构建args移至其他任何东西的顶部,就好像它们在FROM之后的后续步骤中不存在。

编辑:我没有注意到,无论如何我必须重复在每个FROM之后没有值的ARG行...它有效但不理想。

必须从其他阶段显式复制(获取)环境变量。 我有一个有效的用例。
这是一个简化的示例:

FROM node:lts-alpine AS node
FROM php:7-fpm-alpine AS php

# Here goes some build instructions for PHP image, then

# Install nodejs, npm, yarn
COPY --from=node /usr/local/bin/node /usr/local/bin/node
COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules
COPY --from=node /opt /opt

# Create symlinks to npm, yarn binaries
RUN \
    ln -s "/usr/local/lib/node_modules/npm/bin/npm-cli.js" /usr/local/bin/npm \
    && ln -s "/usr/local/lib/node_modules/npm/bin/npx-cli.js" /usr/local/bin/npx \
    && ln -s /opt/yarn-v1.??.?/bin/yarn /usr/local/bin/yarn \
    && ln -s /opt/yarn-v1.??.?/bin/yarnpkg /usr/local/bin/yarnpkg

# Some other instructions

现在,如果在环境变量的节点图像( YARN_VERSION )中定义了纱线版本,我应该如何知道呢?

CI管道具有巨大的优势,可以适应多阶段构建。 诸如“环境继承”之类的功能会将其推向更高级别的维护,并且也是明智的选择。

我大约有5层,大量使用env var,每次更新都是一场噩梦。 好的一面(?),我宁愿三思而后行,再介绍一个新阶段。

当前的ARG实现提供了一些奇怪的行为:

FROM python
ARG X=os    # or ENV, the same  
RUN echo import ${X:-sys}   # Proof that the syntax works with default SHELL
SHELL ["python", "-c"]
RUN import os
RUN import ${X}  # Fails
RUN import ${X:-sys}  # Fails too, but this is a documented syntax (for ENV)

要么

FROM alpine
ARG X=ab.cd
RUN echo ${X%.*}  # it's ok but it looks like magic 
WORKDIR ${X%.*}  # Fails here 

这与ARG是每个阶段的环境变量而不是模板有关。

我知道您(和我)在现实生活中可能从未见过(也许是字符串操作...)。
但这凸显了docker语法基于底层shell语法的事实。 这就是文档的谎言:(请参阅https://docs.docker.com/engine/reference/builder/#environment-replacement和反示例“ RUN import ${X:-sys} ”)

为什么RUN在启动命令之前不像传统的“ make”那样解释ARG?

$ cat Makefile
SHELL=python
X=os
test:
    import os
    import ${X}
$ make test
import os
import os

24小时结束=)

_警告_此解决方案无效,尽管我希望可以!

在第一阶段将env var写入文件,然后在第二阶段复制该文件? 等等? 像这样:

FROM golang:1.14 as cm_base
ARG commit_id
RUN echo "$commit_id" > /tmp/env.json
FROM golang:1.14
COPY --from=cm_base "/tmp/env.json" "/tmp/env.json"
ENV cm_cc_commit_id="$(cat /tmp/env.json)"

繁荣,除了它不起作用外,因为cm_cc_commit_id变成了文字$(cat /tmp/env.json) ,后一个表达式没有被求值,它仍然是文字。 因此,设置动态环境变量似乎是不可能的?

但是,有效的方法是写入文件,将入口点设置为bash文件,然后执行以下操作:

#!/usr/bin/env bash
echo "the docker entrypoint args:" "$@"
export cm_cc_commit_id="$(cat /tmp/env.json)"
"$@"

像这样思考:

Dockerfile没有通过全局ARG声明变量的方法。 这不会自动传播到每个阶段,因为这会影响缓存。
构建器可能会更聪明,可以确定您是否尝试在特定阶段使用ARG并允许它使用,但随后它必须猜测您是否需要文字。

因此,是的,您需要将全局ARG s显式导入到要使用它的每个阶段中。也许对于看起来像您尝试使用ARG情况,构建器或linter都可以实施警告。您尚未导入的
这并没有被设计所破坏,这是在较少的代码行中选择最佳缓存的明确选择。

绝对可以理解,全局ARG s的使用最初是令人困惑的。

此页面是否有帮助?
0 / 5 - 0 等级