如何在Dockerfile中取消设置"ENV"?

51
由于某些原因,我必须在我的Dockerfile中设置“http_proxy”和“https_proxy”的ENV。现在我想取消设置它们,因为还有一些构建过程无法通过代理完成。
# dockerfile

# ... some process

ENV http_proxy=http://...
ENV https_proxy=http://...

# ... some process that needs the proxy to finish

UNSET ENV http_proxy # how to I unset the proxy ENV here?
UNSET ENV https_proxy

# ... some process that can't use the proxy 


这只适用于特定的主机名吗?有一个no_proxy变量可能更适合您的用例。 - BMitch
有一个名为no_proxy的变量可以工作,但它并没有标准化,您可能会在不同的应用程序中获得不同的结果。请参见:链接 - minus one
6个回答

40

这取决于您要实现的效果。

请注意,从实用角度(即开发人员实际使用时),“取消设置变量”可能有两种含义:从环境中删除它或将变量设置为空值。在技术上,这是两个不同的操作。但在实践中,我并没有遇到过一种情况,即软件区分变量不存在于环境中还是存在于环境中但设置为空值。通常可以使用任一方法获得相同的结果。

如果您不关心变量是否在Docker生成的层中,但将其保留为非空值会导致后续构建步骤出现问题。

对于这种情况,可以在Dockerfile中的任意位置使用ENV VAR_NAME=来取消变量的设置。语法说明:Docker允许两种语法形式的ENV: 这个ENV VAR=1ENV VAR 1是相同的。您可以使用空格或等号将变量名称与值分开。当您想通过将其设置为空值来“取消”变量时,必须使用等号语法,否则会在构建时出错。

因此,例如,您可以这样做:

ENV NOT_SENSITIVE some_value
RUN something

ENV NOT_SENSITIVE=
RUN something_else

something 执行时,NOT_SENSITIVE 被设置为 some_value。当 something_else 执行时,NOT_SENSITIVE 被设置为空字符串。

需要注意的是,作为shell命令 进行 unset NOT_SENSITIVE 不会影响除在该 shell 中执行的内容以外的任何其他内容。以下是一个例子:

ENV NOT_SENSITIVE some_value
RUN unset NOT_SENSITIVE && printenv NOT_SENSITIVE || echo "does not exist"

RUN printenv NOT_SENSITIVE

第一个RUN会打印does not exist,因为在printenv执行时NOT_SENSITIVE未设置,并且因为它未设置,printenv返回非零退出码,从而导致执行echo。第二个RUN不受第一个RUN中的unset影响。它将some_value打印到屏幕上。

但如果我需要从环境中删除变量,而不仅仅是将其设置为空值怎么办?

在这种情况下,使用ENV VAR_NAME=是行不通的。我不知道有什么方法告诉Docker“从此时起,您必须从环境中删除此变量,而不仅仅是将其设置为空值”。

如果您仍然想使用ENV来设置变量,则必须以unset VAR_NAME开始每个您希望在其中取消设置变量的RUN,这将仅取消设置该特定的RUN中的变量。

如果要防止变量存在于Docker生成的层中。

假设该变量包含一个机密信息,而该层可能落入不应拥有该机密的人手中。在这种情况下,您无法使用ENV来设置变量。使用ENV设置的变量被嵌入到它适用的层中,并且无法从这些层中删除。特别地,(假设该变量名为SENSITIVE)运行

RUN unset SENSITIVE

不会从该层中删除它。上面的unset命令只会从RUN启动的shell进程中删除SENSITIVE。它只影响那个shell,不会影响通过CMDENTRYPOINT或在命令行上运行docker run提供的任何命令所产生的shell。

为了防止层包含机密信息,我会使用docker build --secret=RUN --mount=type=secret...。例如,假设我已将我的机密信息存储在名为sensitive的文件中,则可以像这样使用RUN

RUN --mount=type=secret,id=sensitive,target=/root/sensitive \
 export SENSITIVE=$(cat /root/sensitive) \
 && [[... do stuff that requires SENSITIVE ]] \
< p >< em >请注意,< code >RUN 命令执行的指令不需要以 < code >unset SENSITIVE 结束。由于进程及其环境管理方式的原因,通过 RUN 产生的 shell 中设置 < code >SENSITIVE 不会对该 shell 所生成的其他子 shell 或 Docker 创建的层中包含的内容产生影响。

然后可以使用以下命令运行构建:

$ DOCKER_BUILDKIT=1 docker build --secret id=secret,src=path/to/sensitive [...]
docker build 命令的环境需要 DOCKER_BUILDKIT=1 才能使用 BuildKit,因为只有当 Docker 使用 BuildKit 构建镜像时,才能使用此方法传递机密信息。

14
如果需要在镜像构建过程中使用环境变量,但不希望它们保留下来,只需清除它们即可。在下面的示例中,运行的容器将显示空的环境变量。
Dockerfile
# set proxy
ARG http_proxy
ARG https_proxy
ARG no_proxy
ENV http_proxy=$http_proxy
ENV https_proxy=$http_proxy
ENV no_proxy=$no_proxy

# ... do stuff that needs the proxy during the build, like apt-get, curl, et al.

# unset proxy
ENV http_proxy=
ENV https_proxy=
ENV no_proxy=

build.sh

docker build -t the-image \
    --build-arg http_proxy="$http_proxy" \
    --build-arg https_proxy="$http_proxy" \
    --build-arg no_proxy="$no_proxy" \
    --no-cache \
    .

run.sh

docker run --rm -i \
    the-image \
    sh << COMMANDS
        env
COMMANDS

输出

no_proxy=
https_proxy=
http_proxy=
...

2
这会在中间层中暴露环境吗? - Dyno Fu
1
这个不起作用(在Debian 9上测试过)。no_proxy是由~/.docker/config.json中的docker设置设置的值。即使您编写“ENV no_proxy =”,这也是将出现在镜像中的值。 - Étienne
这在处理node.js进程时也不起作用--node.js似乎对空的http[s]_proxy环境变量非常不满意 :( - Ferenc
我们实际上不需要 ENV 行。我们可以简单地使用 ARG http_proxy="http://host.docker.internal:3128/" 或者你的代理是什么都可以。 - minus one
如果我们通过传递构建参数来从容器内部覆盖它们以删除它们,那么我们不需要ENV行吗?硬编码的构建参数会存储在历史记录中,对吧? - Alex

12

每一行 ENV 命令都会创建一个新的中间层,就像 RUN 命令一样。这意味着,即使您在将来的层中取消设置环境变量,它仍然存在于当前层中,并且其值可以被输出。您可以通过创建如下所示的 Dockerfile 并构建它来测试这一点。 - minus one

9

简答题:

尽量避免使用不必要的环境变量,这样您就不需要取消设置它们。

如果您必须为一个命令取消设置,可以执行以下操作:

RUN unset http_proxy https_proxy no_proxy \
    && execute_your_command_here  

如果您需要取消构建图像,则可以执行以下操作:
FROM ubuntu_with_http_proxy

ENV http_proxy= \
    https_proxy= \
    no_proxy=

一旦使用ENV指令设置环境变量,我们就无法真正取消设置,如下所述:

每个ENV行都会创建一个新的中间层,就像RUN命令一样。这意味着,即使在以后的层中取消设置环境变量,它仍然存在于该层中,并且其值可以被转储。

请参见:Dockerfile编写最佳实践 详细信息
我更喜欢在构建期间将http_proxy定义为参数,如下所示:
FROM ubuntu:20.04

ARG http_proxy=http://host.docker.internal:3128 
ARG https_proxy=http://host.docker.internal:3128 
ARG no_proxy=.your.domain,localhost,127.0.0.1,.docker.internal


在公司代理中,我们需要进行身份验证,因此需要配置本地代理服务器,监听 127.0.0.1:3128,并通过容器上的 host.docker.internal:3128 进行访问。这样,如果我们通过 VPN(本地/家庭网络被阻止)连接到公司网络,则它也适用于 Docker 桌面版。
设置 no_proxy 也很重要,以避免向代理服务器发送过多请求。
有关 no_proxy 相关主题的更多详细信息,请参见以下文章: 有时候阅读相关文档也是很有帮助的: 如果我们需要配置这些环境变量,可以使用以下命令:
  • 在构建期间(链接):
docker build ... --build-arg http_proxy='http://alternative.proxy:3128/' ...
  • 在运行期间(链接):
docker run ... -env http_proxy='http://alternative.proxy:3128/' ...

请注意,我们甚至不需要定义代理相关的参数,因为这些参数已经根据以下部分预定义了:

Dockerfile reference - Predefined ARGs


0

我发现秘密方法不起作用,因为当我在交互模式下运行容器时,我需要环境变量持久化,但是在后续的生产构建中需要完全删除该变量。

有效的方法是,在开发阶段构建时,将环境变量附加到/root/.basrc文件中。

RUN echo export AWS_PROFILE=role-name >> /root/.bashrc
``

In the production stage of the build I then removed the last line of /root/.bashrc:

运行 sed -i '$ d' /root/.bashrc


0

您可以在Dockerfile中添加以下行:

ENV http_proxy ""
ENV https_proxy ""

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接