图像是不可变的
Dockerfile 定义了镜像的构建过程。一旦构建,图像是不可变的(无法更改)。运行时变量不会被烘焙到这个不可变的图像中。所以 Dockerfile 是解决这个问题的错误地方。
使用入口点脚本
您可能想要做的是用您自己的脚本覆盖默认的ENTRYPOINT,并让该脚本对环境变量执行一些操作。由于入口点脚本将在运行时(容器启动时)执行,因此这是收集环境变量并对其进行处理的正确时间。
首先,您需要调整 Dockerfile 以了解入口点脚本。虽然 Dockerfile 不直接参与处理环境变量,但它仍然需要了解该脚本,因为该脚本将被烘焙到您的镜像中。
Dockerfile:
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
CMD ["npm", "start"]
现在,编写一个入口点脚本,该脚本在命令运行之前完成所需的任何设置,最后,exec 命令本身。
entrypoint.sh:
#!/bin/sh
# Where $ENVSUBS is whatever command you are looking to run
$ENVSUBS < fil1 > file2
npm install
# This will exec the CMD from your Dockerfile, i.e. "npm start"
exec "$@"
在这里,我已将npm install 包括在内,因为您在 cmets 中询问过这个问题。我会注意到这将在每次运行时运行npm install。如果合适的话,很好,但我想指出它每次都会运行,这会给您的启动时间增加一些延迟。
现在重新构建您的映像,因此入口点脚本是其中的一部分。
在运行时使用环境变量
入口点脚本知道如何使用环境变量,但您仍然需要告诉 Docker 在运行时导入该变量。您可以使用 -e 标志到 docker run 来执行此操作。
docker run -e "ENVSUBS=$ENVSUBS" <image_name>
这里告诉 Docker 定义一个环境变量ENVSUBS,它分配的值是当前shell环境中$ENVSUBS的值。
入口点脚本的工作原理
我将对此进行详细说明,因为在 cmets 中,您似乎对它们如何组合在一起有些模糊。
当 Docker 启动一个容器时,它会在容器内执行一个(并且只有一个)命令。该命令变为 PID 1,就像典型 Linux 系统上的 init 或 systemd 一样。该进程负责运行容器所需的任何其他进程。
默认情况下,ENTRYPOINT 是/bin/sh -c。您可以在 Dockerfile 或 docker-compose.yml 中覆盖它,或者使用 docker 命令。
当容器启动时,Docker 运行入口点命令,并将命令 (CMD) 作为参数列表传递给它。之前,我们将自己的ENTRYPOINT 定义为/entrypoint.sh。这意味着在您的情况下,这是 Docker 启动时将在容器中执行的内容:
/entrypoint.sh npm start
因为["npm", "start"] 被定义为命令,所以它作为参数列表传递给入口点脚本。
因为我们使用-e 标志定义了一个环境变量,所以这个入口点脚本(及其子脚本)将可以访问该环境变量。
在入口点脚本的末尾,我们运行exec "$@"。因为$@ 扩展为传递给脚本的参数列表,所以它将运行
exec npm start
因为exec 将其参数作为命令运行,替换当前进程,当你完成后,npm start 成为你容器中的 PID 1。
为什么不能使用多个 CMD
在 cmets 中,您询问是否可以定义多个 CMD 条目来运行多个事物。
您只能定义一个ENTRYPOINT 和一个CMD。在构建过程中根本不使用这些。与RUN 和COPY 不同,它们不会在构建期间执行。它们在构建后作为元数据项添加到图像中。
只有稍后,当图像作为容器运行时,这些元数据字段才会被读取,并用于启动容器。
如前所述,入口点是真正运行的,它通过CMD 作为参数列表传递。它们分开的部分原因是历史原因。在 Docker 的早期版本中,CMD 是唯一可用的选项,ENTRYPOINT 被固定为/bin/sh -c。但是由于这种情况,Docker 最终允许ENTRYPOINT 由用户定义。