在Docker中,“expose”和“publish”的区别是什么?

739

我正在尝试使用Dockerfile进行实验,我认为我已经理解了大部分的逻辑。然而,在这种情况下,我没有看到“暴露”和“发布”端口之间的区别。

我看过的所有教程都首先在Dockerfile中包含EXPOSE命令:

...
EXPOSE 8080
...

他们随后从这个Dockerfile构建镜像:
$ docker build -t an_image - < Dockerfile

然后在运行镜像时,与上面相同的端口进行发布

$ docker run -d -p 8080 an_image

或者使用以下命令发布所有端口:

$ docker run -d -P an_image

如果端口将被发布,那么在Dockerfile中公开端口的意义是什么?是否有必要首先公开一个端口,然后稍后发布它?实际上,我想在创建镜像时在Dockerfile中指定所有将使用的端口,然后再也不用担心它们,只需简单地运行它们:
$ docker run -d an_image

这个可能吗?
8个回答

939

基本上,您有三(四)种选择:

  1. 既不指定 EXPOSE 也不指定 -p
  2. 只指定 EXPOSE
  3. 同时指定 EXPOSE-p
  4. 只指定 -p,这会隐式地执行 EXPOSE
  1. 如果您既不指定 EXPOSE 也不使用 -p,则容器中的服务仅可从容器本身内部访问。

  2. 如果您使用 EXPOSE 暴露端口,则容器中的服务无法从 Docker 外部访问,但可以从其他 Docker 容器内部访问。因此,这对于容器间通信非常有用。

  3. 如果您同时使用 EXPOSE-p 暴露端口,则容器中的服务可以从任何地方访问,甚至是 Docker 外部。

  4. 如果您使用了 -p,但没有使用 EXPOSE,Docker 将隐式执行 EXPOSE。这是因为如果一个端口对公众开放,它也会自动对其他 Docker 容器开放。因此,-p 包括 EXPOSE。这与第3点实际上是相同的。

我认为将两者分开的原因是:

  • 选择主机端口取决于主机,因此不属于Dockerfile(否则它将依赖于主机),
  • 通常情况下,容器中的服务可以从其他容器访问,这已经足够了。

文档明确指出:

EXPOSE指令公开端口以供链接使用。

它还指向了如何链接容器(遗留功能),这基本上就是我谈到的容器间通信。


95
我认为你对EXPOSE的理解不正确。从其他容器中,即使没有显式地暴露端口,也可以访问所有容器端口。我尝试过了。但问题在于容器IP地址是不可预测的。我相信链接(link)用于指定要连接的容器(所以您链接到特定的容器IP),而不是仅仅启用连接。 - Jiri
7
如果你没有指定其中任何一个,最好能够通过解释清楚“其中”指的是 EXPOSE-p,而不是前面的三个项目符号所列举的内容。这样会更容易理解。 - Pithikos
7
文档不再说明“EXPOSE指令用于在链接中公开端口”。 - Lorin Hochstein
83
因为这篇文章严重错误,所以我要给它点个踩。Exposure基本上是文档记录,不使用它并不会限制访问。如果有人依赖它来限制访问,这将是一个非常危险的误解。 - mc0e
11
看起来你的答案已经过时了。修改答案以指出这一事实,并链接到下面@tgogos的答案可能会有帮助(在撰写此评论时),链接:https://dev59.com/AmEh5IYBdhLWcg3wtFX4#47594352 - Moha the almighty camel
显示剩余11条评论

372

简短回答:

  • EXPOSE 是一种文档化的方式
  • --publish (或 -p)是一种将主机端口映射到运行中的容器端口映射方式

请注意以下内容:

  • EXPOSEDockerfiles相关(用于文档编写
  • --publishdocker run ...相关(用于执行/运行时

暴露和发布端口

在Docker网络中,有两种直接涉及网络端口的机制:暴露和发布端口。这适用于默认桥接网络和用户定义的桥接网络。

  • 您可以使用Dockerfile中的EXPOSE关键字或--expose标志来暴露端口。暴露端口是一种记录使用哪些端口的方式,但不会映射或打开任何端口。暴露端口是可选的。

  • 您可以使用--publish--publish-all标志来docker run发布端口。这告诉Docker在容器的网络接口上打开哪些端口。当端口被发布时,它将映射到主机机器上的一个可用高阶端口(高于30000),除非您在运行时指定要映射到主机机器上的端口。您不能在构建镜像时(在Dockerfile中)指定要映射到主机机器上的端口,因为没有办法保证在运行图像的主机机器上该端口可用。

来源:Docker container networking

更新于2019年10月:上述文本已不再出现在文档中,但存档版本在此处:docs.docker.com/v17.09/engine/userguide/networking/#exposing-and-publishing-ports

也许当前的文档如下:

发布端口

默认情况下,创建容器时,它不会将任何端口发布到外部服务或未连接到容器网络的Docker容器。要使端口对Docker之外的服务或未连接到容器网络的Docker容器可用,请使用--publish-p标志。这将创建一个防火墙规则,将容器端口映射到Docker主机上的端口。

可在此处找到:docs.docker.com/config/containers/container-networking/#published-ports

另外,

EXPOSE

EXPOSE指令不会实际发布端口。它作为一种文档的形式,在构建镜像的人和运行容器的人之间记录哪些端口应该被发布。

来自:Dockerfile 参考手册






在未定义EXPOSE / --publish时访问服务:

@Golo Roden's的答案中,有如下陈述:

"如果你没有指定任何一个选项,容器内的服务只能从容器内部访问。"

也许在回答写作时是这种情况,但现在似乎即使您不使用EXPOSE--publish,同一网络的host和其他containers也能够访问您在该容器内启动的服务。

如何测试:

我使用了以下Dockerfile。基本上,我从Ubuntu开始并安装了一个小型Web服务器:

FROM ubuntu
RUN apt-get update && apt-get install -y mini-httpd

我使用"testexpose"构建镜像,并使用以下命令运行新容器:

docker run --rm -it testexpose bash

在容器中,我启动了几个mini-httpd实例:

root@fb8f7dd1322d:/# mini_httpd -p 80
root@fb8f7dd1322d:/# mini_httpd -p 8080
root@fb8f7dd1322d:/# mini_httpd -p 8090

然后我就能够使用主机或其他容器中的 curl 命令获取 mini-httpd 的主页。


更多阅读

Ivan Pepelnjak撰写的关于该主题的非常详细的文章:


43
现在这个答案才是正确的答案。接受的答案似乎基于之前的版本。 - Luke W
10
EXPOSE 不仅仅是文档,它确实有作用。如果你在运行时加上 -P 标志,它会将“所有已暴露的端口映射到随机端口”进行发布。请注意,不要改变原始意思。 - papiro
不发布任何端口的部分是错误的。我刚试过了,它根本不起作用,我也不指望它能起作用。我在compose中也尝试了类似的操作,使用相同网络上的服务名称,但那也不起作用。另外,根据papiro的评论,EXPOSE在使用-P时确实会产生影响。 - Software Engineer
1
该部分:“当未定义EXPOSE / --publish时的服务访问”仍然有效,并且按照上述描述工作(我刚刚再次测试了它)。也许您是在Windows或Mac上尝试它(那里的网络更加复杂)?在Linux VM上,它完全按照所写的方式工作,示例是完整和可验证的。 - tgogos

15

请参考官方文档:https://docs.docker.com/engine/reference/builder/#expose

EXPOSE 允许您在镜像构建时定义要暴露的私有(容器)和公共(主机)端口,当您使用 -P 运行容器时,它们将在容器运行时暴露出来。

$ docker help run
...
  -P, --publish-all                    Publish all exposed ports to random ports
...

公共端口和协议是可选的,如果没有指定公共端口,则Docker会在主机上选择一个随机端口来暴露Dockerfile中指定的容器端口。

最好的做法是不指定公共端口,因为这将限制每个主机只能有一个容器(第二个容器将抛出端口已在使用的错误)。

您可以在docker run中使用-p来控制暴露的容器端口将连接到哪个公共端口。

无论如何,如果您既不使用EXPOSE(使用-P在docker run中),也不使用-p,则不会暴露任何端口。

如果您总是在docker run中使用-p,则不需要EXPOSE,但如果您使用EXPOSE,则您的docker run命令可能会更简单。 EXPOSE可能有用,如果您不关心在主机上暴露哪个端口,或者您确信只有一个容器将被加载。


当你在 Dockerfile 中 EXPOSE 端口号时,记得使用 -P 参数来调用 docker run。 - KunYu Tsai
我希望这篇文章中的语言对于像我这样对某些概念较新的人来说更加清晰易懂。 - BBaysinger
BBaysinger,您想要澄清哪个概念? - ton

12

你可以在 Dockerfile 中使用 EXPOSE 关键字或使用 docker run 命令的 --expose 标志来暴露端口。暴露端口是一种记录使用哪些端口的方式,但不会实际映射或打开任何端口。暴露端口是可选的。

来源:Github 提交记录


6
个人认为措辞不太好? 如果只是文档记录,他们可以选择...我不知道,除了“曝光”之外的任何词,哈哈。 - dtc
1
在CLI中使用--expose PORT真的没有任何作用吗?我可以理解它在Dockerfile中的位置,但为什么要将其作为cli标志呢? - Qwerty

6
大多数人使用 Docker Compose 来创建网络。文档 中指出:

Docker 网络功能支持在网络内创建无需暴露端口的网络,详细信息请参见此功能概述。

这意味着,如果您在容器之间使用网络进行通信,则无需担心暴露端口。

1

“EXPOSE”关键字允许容器所有者告知其他人容器将主要使用哪些端口。

即使您没有在“EXPOSE”中指定端口,也可以发布任何端口。

例如,我们创建了一个使用nginx镜像的Dockerfile并公开端口1234。

FROM nginx:latest
EXPOSE 1234

构建镜像:

docker build -t porttest .

发布容器并将80端口映射到本地80端口:

docker run -p 80:80 porttest

访问localhost:80可查看Nginx默认页面。 Nginx默认页面截图


1
但不幸的是,这并不能回答问题,即是否有一种方法可以利用实际在镜像中公开的端口而无需在容器创建期间显式发布它。答案是:“是的,您可以使用'docker run'的-P选项来发布Dockerfile中公开的所有端口”。 - Michael S.

0
  1. 暴露 - 只允许特定端口与容器连接,它将仅用作“容器间通信”
  2. -p (发布) 将主机端口映射到容器端口 (您已在步骤 1 或 Docker 文件中公开的端口),一旦公开,您将需要映射它,因此我们使用发布,然后它将访问容器的外部世界/互联网。

-7

EXPOSE 用于将本地端口映射到容器端口。例如,如果您在 Docker 文件中指定了 expose:

EXPOSE 8090

那么它将把本地主机的 8090 端口映射到容器的 8090 端口。


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