如何在Dockerfile的CMD中使用变量?

249

在我的Dockerfile文件中:

ENV PROJECTNAME mytestwebsite
CMD ["django-admin", "startproject", "$PROJECTNAME"]

错误:

CommandError: '$PROJECTNAME' is not a valid project name

在这里,最快的解决方法是什么?Docker是否有计划在以后的版本中“修复”或引入此功能?

注意:如果我从Docker文件中删除CMD行,然后运行Docker容器,我可以在容器内部手动运行Django-admin startproject $ PROJECTNAME并创建该项目...


1
你是如何定义 $PROJECTNAME 的?何时进行定义? - Piotr Wittchen
在我的 Dockerfile 开始处使用 ENV。另外我忘了提到,如果我从 Dockerfile 中删除 CMD 行,然后运行容器,在容器内部可以运行此命令并创建项目(这意味着 ENV 变量是有效的)。 - david
你指的是哪种类型的变量:Dockerfile 变量还是环境变量(例如您系统运行时使用的变量)? - Mike Doe
1
CMD ["sh", "-c", "your command with ${any ENV} here"] - vanduc1102
6个回答

346

当您使用执行列表时,例如...

CMD ["django-admin", "startproject", "$PROJECTNAME"]

如果使用 Docker 执行给定的命令,则会直接执行,无需涉及 shell。由于没有涉及 shell,这意味着:

  • 不进行变量扩展
  • 不进行通配符扩展
  • 不进行 I/O 重定向(如 ><| 等)
  • 不通过 command1; command2 运行多个命令
  • 等等。

如果您想让 CMD 扩展变量,则需要安排一个 shell。您可以像这样做:

CMD ["sh", "-c", "django-admin startproject $PROJECTNAME"]

或者您可以使用简单字符串代替执行列表,这将使结果与上一个示例基本相同:

CMD django-admin startproject $PROJECTNAME

5
更多有关Docker问题跟踪器的阅读材料:https://github.com/docker/docker/issues/5509 - jannis
4
如果我使用这些技巧之一,那么我将无法使用CTRL+C停止我的容器。有人找到了解决方案吗? - mr.bjerre
这里没有任何技巧!本回答中的内容通常不会影响您复制容器,如果某些行为与您的预期不符,请提供详细信息并开启新问题,我们会尽力帮助您解决问题。 - larsks
5
我想,应该使用 exec 命令来替换 bash,以启动新进程。这可能与接收信号和 CTRL+C 有关。可以使用类似于 CMD ["sh", "-c", "exec django-admin startproject $PROJECTNAME"] 的方式。 - user3132194
1
请参考@Asimandia在https://dev59.com/31kS5IYBdhLWcg3wCSdx#73334944中提供的简单方法来支持信号转发。 - Donatello
显示剩余2条评论

57

如果你想在运行时使用该值,请在 Dockerfile 中设置 ENV 值。如果你想在构建时使用它,则应使用 ARG

示例:

ARG value
ENV envValue=$value
CMD ["sh", "-c", "java -jar ${envValue}.jar"]

在构建命令中传递值:

docker build -t tagName --build-arg value="jarName"

25

你还可以使用 exec 来同时处理信号和环境变量,这是目前已知的唯一方式。在尝试实现类似优雅关闭的功能时可能会有所帮助。参考自Docker github上的评论

示例:

ENV PROJECTNAME mytestwebsite 
CMD exec django-admin startproject $PROJECTNAME

3
没人看到你的答案(我想要添加的),但这绝对是最好的解决方案 ;) Translated: No one saw your answer (that I wanted to add), but this is definitely the best solution ;) - Donatello
3
这是正确的答案,而不是目前被接受的那个答案,后者不能正确处理操作系统信号!将操作系统信号传播到可执行文件是很重要的。例如,在Kubernetes中,信号被用于优雅地关闭应用程序。 我只想补充一点,即通常更喜欢使用“json数组”语法来表示CMD,例如一些代码检查器会强制执行:https://github.com/hadolint/hadolint/wiki/DL3025 因此,我建议修改示例以使用该语法。 - h3r3
1
@h3r3 所以它应该看起来像这样 CMD ["exec django-admin startproject $PROJECTNAME"] - ahaertig
确实,这是正确的答案。可以确认sigterm被尊重。关于json数组的使用,@h3r3,我们是否有同样的问题,即无法在命令中使用变量? - Bronumski

13

假设您想在容器内启动一个Java进程:

Dockerfile示例摘录:

ENV JAVA_OPTS -XX +UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=1 -XshowSettings:vm 
... 
ENTRYPOINT ["/sbin/tini", "--", "entrypoint.sh"] 
CMD ["java", "${JAVA_OPTS}", "-myargument=true"]

示例entrypoint.sh摘录:

#!/bin/sh 
... 
echo "*** Startup $0 suceeded now starting service using eval to expand CMD variables ***"
exec su-exec mytechuser $(eval echo "$@")

2

对于Java开发人员,按照以下解决方案会起作用:

如果您尝试使用像下面这样的Dockerfile运行容器

ENTRYPOINT ["/docker-entrypoint.sh"]
# does not matter your parameter $JAVA_OPTS wrapped as ${JAVA_OPTS}
CMD ["java", "$JAVA_OPTS", "-javaagent:/opt/newrelic/newrelic.jar", "-server", "-jar", "app.jar"]

以下是一个带有ENTRYPOINT shell脚本的示例:
#!/bin/bash
set -e
source /work-dir/env.sh
exec "$@"

它将正确构建图像,但在容器运行期间打印下面的错误:

Error: Could not find or load main class $JAVA_OPTS
Caused by: java.lang.ClassNotFoundException: $JAVA_OPTS

相反,Java可以通过命令行或通过环境变量_JAVA_OPTIONS读取命令行参数。这意味着我们可以通过_JAVA_OPTIONS传递所需的命令行参数,而无需更改Dockerfile,使其能够作为容器的父进程启动以便通过exec "$@"进行有效的Docker信号处理。

以下是我的最终版本的Dockerfiledocker-entrypoint.sh文件:

...
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["java", "-server", "-jar", "app.jar"]

#!/bin/bash
set -e
source /work-dir/env.sh
export _JAVA_OPTIONS="-XX:+PrintFlagsFinal"
exec "$@"

当你构建完Docker镜像并试图运行它后,你将看到下面的日志,这意味着它已经成功运行:

Picked up _JAVA_OPTIONS: -XX:+PrintFlagsFinal
[Global flags]
      int ActiveProcessorCount                     = -1                                        {product} {default}

0
受以上启发,我做了这个:
#snapshot by default. 1 is release.
ENV isTagAndRelease=0

CMD     echo is_tag: ${isTagAndRelease} && \
        if [ ${isTagAndRelease} -eq 1 ]; then echo "release build"; mvn -B release:clean release:prepare release:perform; fi && \
        if [ ${isTagAndRelease} -ne 1 ]; then echo "snapshot build"; mvn clean install; fi && \ 
       .....

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