如何通过dockerfile在ENTRYPOINT之前执行一个shell命令

我有我的nodejs项目的以下文件

FROM node:boron # Create app directory RUN mkdir -p /usr/src/app WORKDIR /usr/src/app # Install app dependencies COPY package.json /usr/src/app/ RUN npm install # Bundle app source COPY . /usr/src/app # Replace with env variable RUN envsubs < fil1 > file2 EXPOSE 8080 CMD [ "npm", "start" ] 

我运行docker容器与-e标志提供环境variables

但我没有看到替代品。 当envvariables可用时,运行命令是否会被执行?

图像是不可变的

Dockerfile定义了一个图像的构build过程。 一旦build成,图像是不可改变的(不能改变)。 运行时variables不会被烘焙到这个不变的图像中。 所以Dockerfile是解决这个问题的错误地方。

使用入口点脚本

你可能想要做的是用你自己的脚本覆盖默认的ENTRYPOINT ,并让脚本对环境variables做些什么。 由于入口脚本将在运行时(容器启动时)执行,因此这是收集环境variables并对其执行操作的正确时间。

首先,您需要调整Dockerfile以了解入口点脚本。 尽pipeDockerfile并不直接涉及处理环境variables,但仍需要了解该脚本,因为该脚本将被烘焙到您的映像中。

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, ie "npm start" exec "$@" 

在这里,我已经包含了npm install ,因为你在评论中询问了这个问题。 我会注意到这将在每次运行时运行npm install 。 如果这是合适的,那就好了,但是我想指出它会每次都运行,这会给启动时间增加一些延迟。

现在重build你的图像,所以入口脚本是它的一部分。

在运行时使用环境variables

入口点脚本知道如何使用环境variables,但是你仍然必须告诉Docker在运行时导入variables。 你可以使用-e标志来docker run

 docker run -e "ENVSUBS=$ENVSUBS" <image_name> 

在这里,Docker被告知定义一个环境variablesENVSUBS ,它被赋值的值是当前shell环境中的$ENVSUBS的值。

入口点脚本如何工作

我会在这里详细说明一下,因为在评论中,似乎你对这个如何组合起来有点模糊。

当Docker启动一个容器时,它会在容器中执行一个(且只有一个)命令。 该命令变成PID 1,就像典型的Linux系统上的initsystemd一样。 这个过程负责运行容器需要的任何其他进程。

默认情况下, ENTRYPOINT/bin/sh -c 。 您可以在Dockerfile或docker-compose.yml中或使用docker命令覆盖它。

当容器启动时,Docker运行入口命令,并将命令( CMD )作为参数列表传递给它。 早些时候,我们将自己的ENTRYPOINT定义为/entrypoint.sh 。 这意味着在你的情况下,这是Docker在启动时将在容器中执行的内容:

 /entrypoint.sh npm start 

因为["npm", "start"]被定义为命令,所以作为参数列表传递给入口点脚本。

因为我们使用-e标志定义了一个环境variables,所以这个入口点脚本(及其子)将有权访问该环境variables。

在入口脚本的末尾,我们运行exec "$@" 。 因为$@扩展到传递给脚本的参数列表,所以会运行

 exec npm start 

而且由于exec以命令的forms运行它的参数,所以当你完成后, npm start会在你的容器中变成PID 1。

为什么你不能使用多个CMD

在评论中,你问是否可以定义多个CMD条目来运行多个事情。

您只能定义一个ENTRYPOINT和一个CMD 。 这些在构build过程中根本不使用。 与RUNCOPY不同,它们在构build期间不会执行。 一旦构build完成,它们将作为元数据项添加到映像中。

只有当图像作为容器运行时,才会读取这些元数据字段,并用于启动容器。

如前所述,入口点是真正运行的,并且将CMD作为参数列表传递。 他们分开的原因部分是历史的。 在早期版本的Docker中, CMD是唯一可用的选项,并且ENTRYPOINT被固定为/bin/sh -c 。 但由于像这样的情况,Docker最终允许用户定义ENTRYPOINT

当envvariables可用时,运行命令是否会被执行?

使用-e标志设置的环境variables在run容器时设置。

问题是Dockerfile是在容器的build上读取的,所以RUN命令不会意识到这个环境variables。

在build上设置环境variables的方法是在Dockerfile中添加ENV行。 ( https://docs.docker.com/engine/reference/builder/#/environment-replacement

所以你的Dockerfile可能是:

 FROM node:latest WORKDIR /src ADD package.json . ENV A YOLO RUN echo "$A" 

而输出:

 $ docker build . Sending build context to Docker daemon 2.56 kB Step 1 : FROM node:latest ---> f5eca816b45d Step 2 : WORKDIR /src ---> Using cache ---> 4ede3b23756d Step 3 : ADD package.json . ---> Using cache ---> a4671a30bfe4 Step 4 : ENV A YOLO ---> Running in 7c325474af3c ---> eeefe2c8bc47 Removing intermediate container 7c325474af3c Step 5 : RUN echo "$A" ---> Running in 35e0d85d8ce2 YOLO ---> 78d5df7d2322 

RUN命令启动时,您会在前一行看到,容器知道设置了环境variables。