如何在ENTRYPOINT数组中使用Docker环境变量?

227
如果我设置一个环境变量,例如ENV ADDRESSEE=world,并且我想在入口点脚本中将其与固定字符串连接起来使用:

如果我设置一个环境变量,比如说ENV ADDRESSEE=world,然后我想把它加到一个固定的字符串里面:

ENTRYPOINT ["./greeting", "--message", "Hello, world!"]

如果环境变量的值为world,我该怎么做?我尝试使用"Hello, $ADDRESSEE",但似乎不起作用,因为它会将$ADDRESSEE文本字面上解释。

11个回答

374
你正在使用 ENTRYPOINT 的 exec 形式。与 shell 形式 不同,exec 形式 不会调用命令 shell。这意味着不会发生正常的 shell 处理。例如,ENTRYPOINT [ "echo", "$HOME" ] 不会对 $HOME 进行变量替换。如果你想要进行 shell 处理,可以使用 shell 形式 或者直接执行 shell,例如: ENTRYPOINT [ "sh", "-c", "echo $HOME" ]
在使用 exec 形式并直接执行 shell 的情况下,就像使用 shell 形式一样,是 shell 在进行环境变量扩展,而不是 docker。(来自Dockerfile 参考文档
在你的案例中,我将使用 shell 形式
ENTRYPOINT ./greeting --message "Hello, $ADDRESSEE\!"

4
为什么无法解析端口 ENV,当 ENV port=123 时,ENTRYPOINT java -jar /dockertest.jar -Djava.security.egd=file:/dev/./urandom -Dserver.port=$port 命令不能正常工作。 - xetra11
4
虽然它起作用了,但似乎出现了一些新问题,比如没有将传递给入口点的参数包括在内。例如,您无法向docker run命令添加一个--attitude "shouting"参数,该参数应该被传递给./greeting - Daniel F
13
如果您想通过 docker run 命令传递额外的变量给 ./greeting,或者传递 Dockerfile 的 CMD,请使用 ENTRYPOINT ./greeting --message "Hello, $ADDRESSEE\! $0 $@" - Daniel F
10
请注意,使用shell形式可能会导致信号无法传递给进程(例如您的示例中的greeting)。https://hynek.me/articles/docker-signals/ - jbg
1
@jtlz2 很多容器没有外壳,这可能会干扰你的应用程序。 - Brandon
显示剩余4条评论

33

经过很多煎熬,以及来自上面的@vitr等人的极大帮助,我决定尝试使用

  • 标准Bash替换
  • ENTRYPOINTshell形式(来自上述的绝佳贴士)

然后就可以了。

ENV LISTEN_PORT=""

ENTRYPOINT java -cp "app:app/lib/*" hello.Application --server.port=${LISTEN_PORT:-80}
docker run --rm -p 8080:8080 -d --env LISTEN_PORT=8080 my-image

docker run --rm -p 8080:80 -d my-image

两者都在我的容器中正确设置了端口。

参考资料

请参阅https://www.cyberciti.biz/tips/bash-shell-parameter-substitution-2.html


28

我尝试使用提供的答案解决问题,但仍然遇到了一些问题...

以下是解决我的问题的方案:

ARG APP_EXE="AppName.exe"
ENV _EXE=${APP_EXE}

# Build a shell script because the ENTRYPOINT command doesn't like using ENV
RUN echo "#!/bin/bash \n mono ${_EXE}" > ./entrypoint.sh
RUN chmod +x ./entrypoint.sh

# Run the generated shell script.
ENTRYPOINT ["./entrypoint.sh"]

特别针对您的问题:

RUN echo "#!/bin/bash \n ./greeting --message ${ADDRESSEE}" > ./entrypoint.sh
RUN chmod +x ./entrypoint.sh
ENTRYPOINT ["./entrypoint.sh"]

你的回答似乎没有提供完整的解决方案来回应问题。 - user9405863
我想我不明白它为什么不能提供一个解决方案给楼主的问题... 我更新了一个例子,以解决这个确切的问题。 - Ben Kauffman
你提到了仍然遇到了一些问题!! - user9405863
1
正确,这就是为什么我介绍了一个新的解决方案。"被接受"的答案对我不起作用,所以我将其回显到一个shell脚本中,那样就可以工作了。 - Ben Kauffman
将脚本写入Dockerfile文件中,这是正确的做法吗?我认为不是。 - Reverend Tim
9
我很感兴趣听听你的方法,@ReverendTim ;) - Ben Kauffman

15
对我来说,我想把脚本的名称存储在一个变量中,并且仍然使用exec形式。
注意:确保你尝试使用的变量是通过命令行或通过ENV指令声明的环境变量。
最初我做了这样的事情:
ENTRYPOINT [ "${BASE_FOLDER}/scripts/entrypoint.sh" ]

但显然这并没有起作用,因为我们正在使用shell表单,而第一个列出的程序需要是PATH上的可执行文件。所以为了解决这个问题,我最终做了以下操作:
ENTRYPOINT [ "/bin/bash", "-c", "exec ${BASE_FOLDER}/scripts/entrypoint.sh \"${@}\"", "--" ]

请注意,双引号是必需的。
这样做的目的是允许我们获取传递给/bin/bash的任何额外参数,并在bash解析名称后将这些参数传递给我们的脚本。

man 7 bash

--
-- 表示选项的结束,禁用进一步的选项处理。-- 后的任何参数都被视为文件名和参数。- 的参数等同于 --。

P.S. -- 也适用于任何符合 POSIX 标准的 shell。请参阅 Utility Syntax Guides


5
这个很好用。如果没有"--",bash会把第一个参数留给自己而不传递给脚本,而加上"--"后就可以按预期运行了。但需要两点改进。1)命令应该以exec开头:"exec ${BASE_FOLDER}/..."。否则,脚本/可执行文件无法正确接收信号。2)应该使用\"${@}\"而不仅仅是${@},以正确处理传递给CMD的带引号的参数。 - Mike
@Mike,哪些信号不会被接收到? - smac89
4
根据ENTRYPOINT文档,如果Shell是父进程,则在运行docker stop ...时将不会向您的脚本传播SIGTERM信号。 您可以搜索有关“docker pid 1 signal”的更多信息,或者例如在Google的最佳实践中了解更多相关信息。 另外,请注意,对于ENTRYPOINT数组元素,不允许使用单引号,它们需要用双引号括起来。 - Mike
@Mike,我认为添加exec不会解决那个问题。如果一个脚本包含以下内容:sleep 10;echo foo | tee --append /tmp/foo.out(不要忘记#!/bin/bash),我期望如果我们像你建议的那样使用exec运行这个脚本,然后立即按下CTRL+Z即SIGSTOP,那么sleep命令也会停止,但事实并非如此。sleep命令继续运行,如果在恢复脚本之前等待足够长的时间,它将不会继续睡眠,而是立即打印foo。也许我在这里漏掉了什么,但这是你期望的吗? - smac89
2
你所描述的情况是当bashENTRYPOINT启动并执行另一个运行脚本的bash时。如果我们假设bash在处理信号方面存在问题,则不应将其用作要测试的实际进程。其次,我不确定您使用的平台是什么,通常Ctrl-Z会发送SIGTSTP而不是SIGSTOP。为了真正测试发生的情况,您需要引起SIGTERM信号,该信号在运行docker stop时生成。 - Mike
显示剩余3条评论

13

我很简单地解决了这个问题!

重要提示:你希望在 ENTRYPOINT 中使用的变量必须是ENV类型(而不是ARG类型)。

示例 #1:

ARG APP_NAME=app.jar                    # $APP_NAME can be ARG or ENV type.
ENV APP_PATH=app-directory/$APP_NAME    # $APP_PATH must be ENV type.
ENTRYPOINT java -jar $APP_PATH

执行以下命令将会得到这个结果: java -jar app-directory/app.jar

示例 #2 (你的问题):

ARG ADDRESSEE="world"                       # $ADDRESSEE can be ARG or ENV type.
ENV MESSAGE="Hello, $ADDRESSEE!"            # $MESSAGE must be ENV type.
ENTRYPOINT ./greeting --message $MESSAGE

执行如下命令将得到:

./greeting --message Hello, world!
  • 请确认在分配字符串变量时是否需要使用引号""

提示:尽可能使用ENV代替ARG,以避免在您自己或SHELL方面产生混淆。


2
以防万一,可以将环境变量设置为可自定义的:ARG TEST; ENV TEST="${TEST:-foo}"。相关链接:https://docs.docker.com/compose/environment-variables/#substitute-environment-variables-in-compose-files https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html - Artfaith
这假设/bin/sh是可用的。:( - undefined

3
在我的情况下,这样做有效:(适用于docker中的Spring Boot应用)
ENTRYPOINT java -DidMachine=${IDMACHINE} -jar my-app-name

并在 Docker 运行时传递参数。

docker run --env IDMACHINE=Idmachine -p 8383:8383 my-app-name

请注意,当您使用shell-Form时,您的Java进程将不会接收到SIGTERM信号,因为该信号被发送到PID 1。而PID = 1的进程是Shell进程。因此,您的Java进程只能通过SIGKILL被强制杀死,无法正常关闭。请参阅https://hynek.me/articles/docker-signals/。 - aubium77
这假设/bin/sh是可用的。 :( - undefined

1

如果有人想要在ENTRYPOINT的exec形式中传递ARGENV变量,则可以使用镜像构建过程中创建的临时文件。

在我的情况下,我必须根据.NET应用程序是否已发布为自包含的应用程序来以不同的方式启动应用程序。 我创建了临时文件,并在我的bash脚本的if语句中使用了它的名称。

Dockerfile的一部分:

ARG SELF_CONTAINED=true #ENV SELF_CONTAINED=true also works
# File has to be used as a variable as it's impossible to pass variable do ENTRYPOINT using Exec form. File name allows to check whether app is self-contained
RUN touch ${SELF_CONTAINED}.txt
COPY run-dotnet-app.sh .
ENTRYPOINT ["./run-dotnet-app.sh", "MyApp" ]

run-dotnet-app.sh:

#!/bin/sh

FILENAME=$1

if [ -f "true.txt" ]; then
   ./"${FILENAME}"
else
   dotnet "${FILENAME}".dll
fi


1
之前的答案建议使用 shell 形式。 在我的情况下,这不是一个选项,因为使用它会导致信号无法到达进程本身。
请参见此处的第1点https://hynek.me/articles/docker-signals/ 如果 JSON 语法可以解析变量,那么这就是我想要的内容:
ENTRYPOINT ["${APP_NAME}"]

如果您创建一个文件以这样的方式运行:
RUN echo "#!/bin/bash \n ${APP_NAME}" > ./entrypoint.sh

你也会失去信号,因为一个新的 shell 将被创建。

请参考第二点并使用 exec

最终对我有效的形式:

RUN echo "#!/bin/bash \n exec ${APP_NAME}" > ./entrypoint.sh
RUN chmod +x ./entrypoint.sh
ENTRYPOINT ["./entrypoint.sh"]

我在类似的命令上收到了“exec ./entrypoint.sh: no such file or directory”的错误消息:RUN echo "#!/bin/bash \n set -e \n geth --unlock 0x367103555b34Eb9a46D92833e7293D540bFd7143 --password /tmp/pwd.txt --keystore /tmp/keystore \n" > ./tmp/entrypoint.sh,但是当我将其放入主机上的单独文件中并执行COPY时,它可以正常工作。有人发帖说可能存在“行尾”问题。 - Gleichmut

1

我使用一种变体的“创建自定义脚本”方法解决了这个问题。方法如下:

FROM hairyhenderson/figlet
ENV GREETING="Hello"
RUN printf '#!/bin/sh\nfiglet -W \${GREETING} \$@\n' > /runme && chmod +x /runme
ENTRYPOINT ["/runme"]
CMD ["World"]

像疾风一样奔跑

docker container run -it --rm -e GREETING="G'Day" dockerfornovices/figlet-greeter Alec

这假设 /bin/sh 是可用的。 :( - undefined

1

这是对我有效的方法:

ENTRYPOINT [ "/bin/bash", "-c", "source ~/.bashrc && ./entrypoint.sh ${@}", "--" ]

现在您可以向docker run命令提供任何参数,仍然可以读取所有环境变量。


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