如果您曾经需要在启动时在Docker容器中运行一两个命令,那么这个教程适合您。使用Dockerfile ENTRYPOINT
和CMD
指令,您可以运行尽可能多的启动命令。
在本教程中,您将学习如何使用ENTRYPOINT
和CMD
指令在Dockerfile中运行启动命令,并了解它们之间的区别。
先决条件
由于本教程将进行实际演示,请确保您已经做好了以下准备:
- A Windows 10 PC – Windows 10 v10.0.19042 was used in this tutorial.
- Docker桌面版 – 本教程使用Docker桌面版v3.3.1。
创建Dockerfile
在运行Docker容器启动命令之前,您必须首先创建一个Dockerfile。Dockerfile是一个文本文档,包含构建容器的命令列表,Docker镜像的命令,并确定如何创建Docker镜像。
1. 首先,以管理员身份打开PowerShell。
2. 创建一个新文件夹来存储 Dockerfile 和本教程将使用的所有相关文件,并切换到该目录。本教程使用 ~/docker。
3. 现在,使用以下命令创建一个空白文本文件并命名为 Dockerfile。
或者,如果您使用的是 Linux 或 Mac OS,可以使用以下命令创建 Dockerfile。
4. 最后,将以下内容添加到 Dockerfile 中。
您现在已经创建了一个即将用于 Dockerfile 的文件!
构建 Docker 镜像
现在您已经创建了 Dockerfile,您需要构建一个 Docker 镜像来执行 Dockerfile 中 ENTRYPOINT 和 CMD 指令中编写的命令。构建镜像的一种方法是使用 build
命令。
在 ~/docker 目录中,运行以下命令。下面的命令通过指定当前工作目录 (.
) 在 ~/docker 中的 Dockerfile 创建一个名为 demo 的 Docker 镜像(-t demo
)。

运行 Docker 容器
在构建了 Docker 镜像之后,您将需要一个容器来运行 Docker 镜像,以执行 Dockerfile 中 ENTRYPOINT 和 CMD 指令中的命令。
要运行一个 Docker 容器,调用 run
命令 来创建一个可写的容器层覆盖 Docker 镜像(demo
)。下面的示例使用了 -it
参数与容器进行交互连接,这样您就可以看到示例输出。

Exec vs. Shell Form
当您开始使用 Dockerfile 并弄清楚如何运行启动命令时,您可能会遇到两种不同的定义这些命令的方法。每种方法都会调用命令,但执行方式略有不同。
当 Docker 执行命令时,它可以直接调用 exec
,也可以通过容器的 shell(在 Linux 上为 /bin/sh -c
,在 Windows 上为 cmd /S /C
)调用 shell
。
您会注意到通过 exec
执行的命令具有一个指令,后跟要调用的可执行文件,然后是一个或多个命令行参数,如下所示。
另一方面,以 shell
形式编写命令不需要将命令包装在方括号中,如下所示。
如果您没有指定
CMD
的参数,Docker 将始终以 exec 形式执行命令,例如CMD <command>
。
如果您刚开始,区分这两种命令调用可能不太重要,但随着您变得更加高级,很快您将看到每种方法的优缺点。
运行启动命令
让我们现在深入了解本教程的实质,并通过演示一些在 Dockerfile 的 ENTRYPOINT
和 CMD 指令中运行启动命令的示例来亲自动手。
1. 在您首选的文本编辑器中打开您之前创建的 Dockerfile。
2. 将示例 Dockerfile 的内容复制粘贴到您的 Dockerfile 中,如下所示,并保存。
这个 Dockerfile 使用 ubuntu:20.04
作为基础镜像创建了一个层。然后它告诉 Docker 使用 exec
和 shell
形式,在 Dockerfile 的 CMD
和 ENTRYPOINT
指令中传递 Hello world
参数来调用 echo
命令。
3. 在 ~/docker 目录中,通过运行 docker build
构建新的镜像,并将其命名为 demo
。下面的命令将 标记 这个镜像为 demo
,并在当前工作目录 (.
) 中寻找 Dockerfile。

4. 现在,运行一个容器使用这个镜像,然后运行一个基于之前创建的 Docker 镜像的 Docker 容器。您现在将看到容器返回 Hello world
,这来自 Dockerfile 中提供的 CMD
指令。

在 Dockerfile 中使用变量
有时候你可能事先不知道要传递给命令的确切命令行参数。你需要传递给命令的参数只有在运行时才暴露出来。与其静态地分配命令参数,你可以使用变量来捕获并传递这些参数给命令。
你只能在
shell
形式中使用Dockerfile变量。Docker不支持通过exec
形式调用的命令中的变量。
再次打开你喜欢的文本编辑器中的Dockerfile,用以下一系列命令替换其中的所有内容,并保存。
这一次你会注意到,Dockerfile使用了环境变量,并且使用ENV
进行显示。在下面的例子中,Dockerfile定义了一个名为name
的环境变量,其值为friend
。一旦创建,这个环境变量就可以通过$name
来引用。
当Docker根据这个Dockerfile运行容器时,它会调用echo
命令,并传递Welcome, friend
作为参数。
现在,创建Docker镜像并再次运行容器,可选地提供一个shellform
的标签名。你会注意到Docker调用了echo
命令,并返回了预期的输出。

结合Dockerfile的ENTRYPOINT和CMD指令
大部分时候,您可以在CMD或ENTRYPOINT指令中调用启动命令。毕竟,您可以使用每种方法调用任意数量的命令。但是,您也可以调用单个命令并使用这两个指令“构建”。
基于前面的示例,也许您有一个Dockerfile,看起来像下面的示例。当前,如果您创建一个镜像并从该镜像运行一个容器,Docker将调用echo
命令并返回Hello
。
也许您有另一个参数想传递给echo
命令,但不是立即传递。也许您希望在Dockerfile中的后面执行此操作。通过调用没有命令的CMD
指令,您可以实现这一点。
当您通过
ENTRYPOINT
指令指定要运行的命令,然后是CMD
指令时,Docker会自动假设传递给CMD
的值是一个参数,而不是一个命令。
现在,添加一个没有命令,只有一个名为world
的参数的CMD
指令引用,如下所示。
组合指令应始终使用exec形式编写,因为它的“数组样式”行为可以通过逗号分隔的方式单独指定值,而不是全部放在一个字符串中。
构建镜像并从镜像运行容器后,您会发现Docker只返回一行输出(Hello
和world
),这意味着只执行了一个echo
命令。

结论
你现在应该对通过 CMD
和 ENTRYPOINT
Dockerfile 指令运行 Docker 容器启动命令有了很好的理解。每个指令都有一些不同之处,但都能完成相同的任务,甚至可以一起使用。
你能想到一种情况吗,在这种情况下你更愿意使用 CMD
而不是 ENTRYPOINT
来运行启动命令?