如何为现有的Docker容器分配端口映射?

742

我不确定是否理解有误,但似乎只能通过从镜像创建新的容器来设置端口映射。是否存在一种方式可以将端口映射分配给现有的Docker容器?


4
使用iptables可能会像这个答案所示的方式操作在运行中的Docker容器上公开端口 - Choldrim
6
我猜测这是故意设计的。Docker试图强制使您的操作“可重现”,容器是一种“记录系统”。任何不影响容器的手动步骤都可能会轻易丢失。换句话说:您希望容器代表所有必要的配置才能运作。因此,如果您想打开一个新端口,则需要创建一个新容器。 - Lance Kind
4
这是一个老问题,我不会回答它,但我想说的是,也许你和点赞这个问题和答案的人完全误解了docker的概念。Docker适用于无状态应用程序,可以多次扩展或缩小。在不能重新创建的生产环境中,您永远不应该将某些东西持久化到容器内,如果需要持久化,请映射目录。Docker不是像“轻量级虚拟机”那样的东西,也许你要找的是linuxcontainers.org,lxd基于docker概念,但考虑了“轻量级虚拟机”的概念。 - Edgar Carvalho
如果有帮助的话,可以使用“Kitematic”工具向已运行的容器添加端口映射。这意味着必须有Docker命令来完成完全相同的事情,只需要进行一些谷歌搜索即可 :) 祝好运 - Yaffah
15个回答

658

我也对这个问题很感兴趣。

正如@Thasmo所提到的,端口映射只能在docker run(和docker create)命令中指定。
其他命令,如docker start没有-p选项,docker port仅显示当前的端口映射。

为了添加端口映射,我通常按照以下步骤操作:

  1. 停止正在运行的容器

docker stop test01
  • 提交容器

  • docker commit test01 test02
    

    注意: 上面的test02是我从test01容器构建的新图像。

  • 从已提交的镜像中重新运行

  • docker run -p 8080:8080 -td test02
    
  • 第一个8080是本地端口,第二个8080是容器端口。

  • 27
    如果我想保留test01这个名称怎么办? - user69715
    22
    有人知道Docker是否存在一个开放的问题,允许在docker start时使用端口规范(--publish)吗? - Elijah Lynn
    12
    在这种情况下,这些容量会发生什么变化? - Andrew Savinykh
    129
    这个解决方案很糟糕,我不知道它是如何获得250个赞的。也许那些点赞的人不知道这个解决方案会造成什么混乱。是的,它很糟糕,相当于在不同的端口上运行一个新的容器。 - Arnold Zahrneinder
    56
    也许你想留下一个更好的答案?如果您告诉我们更好的方法,我相信我们都会非常感激。 - crockeea
    显示剩余12条评论

    497
    您可以通过直接编辑hostconfig.json文件来更改端口映射,该文件位于/var/lib/docker/containers/[hash_of_the_container]/hostconfig.json/var/snap/docker/common/var-lib-docker/containers/[hash_of_the_container]/hostconfig.json。如果您安装了Docker作为Snap包,则使用后者。

    您可以通过docker inspect <container_name>命令确定[hash_of_the_container]的值。该值在“Id”字段中。

    1. 停止容器(docker stop <container_name>)。
    2. 停止Docker服务(如Tacsiazuma的评论所述)。
    3. 更改文件。
    4. 重新启动Docker引擎(清除配置缓存)。
    5. 启动容器(docker start <container_name>)。

    因此,您无需创建图像即可完成此操作。您还可以在此处更改重启标志。

    P.S. 您可以访问https://docs.docker.com/engine/admin/,了解如何根据您的主机计算机正确地重新启动Docker引擎。我使用sudo systemctl restart docker来重启我的运行在Ubuntu 16.04上的Docker引擎。


    42
    当 Docker 停止时,它似乎会覆盖您的更改,因此需要进行以下步骤:2. 停止 Docker,3. 修改文件,4. 启动 Docker 引擎。 - Tacsiazuma
    10
    我尝试了上述方法,它有效。如需更多详细信息,请参见: https://mybrainimage.wordpress.com/2017/02/05/docker-change-port-mapping-for-an-existing-container/ - rohitmohta
    27
    为了使这个工作起来,重要的是停止容器、停止Docker引擎,并更改hostconfig.jsonconfig.v2.json两个文件。请使用@rohitmohta提供的链接查看详细信息。 - Kalpak Gadre
    7
    对我有帮助,如果在Mac上使用Docker应用程序,请按照此处的说明进入/ var / lib / docker / containers文件夹:https://dev59.com/s1kT5IYBdhLWcg3weffR#41226917,基本上运行`screen〜/ Library / Containers / com.docker.docker / Data / com.docker.driver.amd64-linux / tty`。一旦TTY运行,您就可以导航到/ var / lib / docker。 - nommer
    23
    请问有人能告诉我 Windows 中容器文件夹的位置吗? - Vijay
    显示剩余15条评论

    74
    如果你所说的“已存在”指的是“正在运行”,那么目前无法添加端口映射。
    但是,如果你需要在不停止/重新启动容器的情况下暴露正在运行的容器中的服务,你可以使用例如Pipework动态添加新的网络接口。

    15
    这应该是最棒的答案。简洁明了,回答了楼主的问题,其他答案都没有做到这点!有时候一个负面的结果也是一种结果! - Partly Cloudy

    42

    编辑hostconfig.json似乎现在不起作用了。它只会将端口公开但不发布到主机上。对我来说,提交和重新创建容器并不是最好的方法。有人提过docker network吗?

    最好的解决方案是在同一网络中使用反向代理

    1. 如果你之前的容器没有加入任何命名网络,请创建一个新的网络。

      docker network create my_network

    2. 将现有的容器加入到创建的网络中

      docker network connect my_network my_existing_container

    3. 启动一个反向代理服务(例如nginx),发布所需的端口,加入同一网络

      docker run -d --name nginx --network my_network -p 9000:9000 nginx

      可选地,删除nginx中的default.conf

      docker exec nginx rm /etc/nginx/conf.d/default.conf

    4. 创建一个新的nginx配置文件

      server
      {
          listen 9000;
      
          location / {
              proxy_pass http://my_existing_container:9000;
              proxy_http_version 1.1;
              proxy_set_header Upgrade $http_upgrade;
              proxy_set_header Connection 'upgrade';
              proxy_set_header Host $host;
              proxy_cache_bypass $http_upgrade;
          }
      }
      

      将配置文件复制到nginx容器中。

      docker cp ./my_conf.conf nginx:/etc/nginx/conf.d/my_conf.conf

    5. 重新启动nginx

      docker restart nginx

    优势:要发布新端口,您可以安全地停止/更新/重新创建nginx容器,而不会影响业务容器。如果您需要实现零停机时间的nginx,可以将更多反向代理服务加入同一网络。此外,一个容器可以加入多个网络。

    编辑:

    要反向代理非HTTP服务,配置文件有些不同。以下是一个简单的示例:

    upstream my_service {
        server my_existing_container:9000;
    }
    
    server {
        listen 9000;
        proxy_pass my_service;
    }
    

    1
    这种方法很神奇且实用,但对于企业系统而言,它似乎是在混淆视听。让单个系统控制工作流程更好。 - Afshin
    1
    @Afshin 对于企业系统或项目来说,我认为这个解决方案比重新创建(会导致停机时间)或黑客攻击hostconfig.json文件更好(至少没有正式引入)。额外的容器只是暴露了您的业务容器的内部端口,而不是对其进行任何更改。 - Sean C.
    1
    很棒的方法。我需要为我的容器配置不同的nginx以使其在代理后正常工作,但这似乎是正确的做法。它也适用于蓝绿部署。 - Brooks DuBois
    2
    不错的方法!我认为使用类似于socat的TCP代理可能会更容易。 - Konstantin Pelepelin
    1
    我喜欢 reproxy,因为它更容易配置:docker create -p EXTERNAL_PORT:8080 --name reproxy_container --network NETWORK_NAME umputun/reproxy --static.enabled --static.rule=*,^/(.*),http://ORIGINAL_CONTAINER:INTERNAL_PORT/$1 - Winand
    @Winand 看起来很不错,而且非常清晰。umputun/reproxy 的 GitHub 网站也非常有信息量。但是它似乎只适用于 http(s),而答案中的 nginx 则涵盖了更多情况。尽管如此,reproxy 似乎是工具集的一个不错的补充。 - quetzalcoatl

    36

    如果您对Docker的深度配置不太熟悉,iptables可能会成为您的朋友。

    iptables -t nat -A DOCKER -p tcp --dport ${YOURPORT} -j DNAT --to-destination ${CONTAINERIP}:${YOURPORT}
    
    iptables -t nat -A POSTROUTING -j MASQUERADE -p tcp --source ${CONTAINERIP} --destination ${CONTAINERIP} --dport ${YOURPORT}
    
    iptables -A DOCKER -j ACCEPT -p tcp --destination ${CONTAINERIP} --dport ${YOURPORT}
    

    这只是一个技巧,不是推荐的方法。这在我的情况下有效,因为我无法停止容器。
    注意:您可以使用-D标志从iptables中删除/恢复/删除规则;如果您要移除容器并希望使用该端口,这将是必要的。

    这是一个很好的答案!谢谢!如果我想将 DOCKER_PORT 映射到 MACHINE_PORT,哪些部分应该更改? - Ciprian Tomoiagă
    请注意,Docker 不会知道这个手动添加的内容。当您稍后使用 Docker 正确地暴露端口重新启动服务时,SO 条目不会被删除。因此,当任何内容发生更改时,请务必非常仔细地检查 iptables。特别是要查找重复的条目! - anthony
    很棒的答案。不要忘记在从本地网络访问docker所需应用程序的计算机的主机文件中进行更改。 - Akif
    我需要运行所有三个命令还是只需运行一个? - Long
    也许我们可以分享一种去除它们的方法。 - MrMesees

    36

    在藤本洋一的例子中,test01是一个容器,而test02是一个镜像。

    在执行docker run之前,您可以删除原始容器,然后再次为容器分配相同的名称:

    $ docker stop container01
    $ docker commit container01 image01
    $ docker rm container01
    $ docker run -d -P --name container01 image01
    

    (使用-P将端口暴露给随机端口而不是手动分配)。


    29
    请注意,根据应用程序内部情况,您将丢失所有数据。 - Barry
    2
    @Barry - 是在什么情况下呢?这会将容器提交为镜像,从而将容器中的所有数据保存到镜像中。原始容器使用的任何卷或挂载当然仍然存在,因为它们与容器和镜像是分开的。所以我不明白。 - CivFan
    2
    我尝试了这个。它成功地删除了我所有的数据库 :/ - Arslan Munir
    2
    数据可以存储在卷上。在删除之前,请尝试检查容器的卷 docker inspect container01,保留卷名称并在运行新容器时挂载它。 - Sergei Vavilov
    @CivFan “原始容器使用的任何卷或挂载肯定仍然存在,因为它们与容器和镜像是分开的。” - 您非常接近于以下内容:是的,存储在卷或挂载中的数据将是安全的。但是,如果数据不在卷或挂载中,它将会丢失 - 而且数据并不总是在卷或挂载中,因此有时会丢失数据。 - cincodenada
    @cincodenada,不,任何在container01中且未在卷、挂载点或原始镜像中的数据将会被保存在image01中。因此产生了混淆。我猜这些数据可能会丢失,就像你在房间里把钥匙丢了一样,但并非永久丢失。你只需要了解一些关于docker的知识就可以找到它。 - CivFan

    34

    如果您运行docker run <NAME>,它将生成一个新的镜像,这很可能不是您想要的。

    如果要更改当前的镜像,请执行以下操作:

    docker ps -a

    获取目标容器的ID并转到:

    cd /var/lib/docker/containers/<conainerID><and then some:)>
    

    停止容器:

    docker stop <NAME>
    

    更改文件

    vi config.v2.json
    
    "Config": {
        ....
        "ExposedPorts": {
            "80/tcp": {},
            "8888/tcp": {}
        },
        ....
    },
    "NetworkSettings": {
    ....
    "Ports": {
         "80/tcp": [
             {
                 "HostIp": "",
                 "HostPort": "80"
             }
         ],
    

    并更改文件

    vi hostconfig.json
    
    "PortBindings": {
         "80/tcp": [
             {
                 "HostIp": "",
                 "HostPort": "80"
             }
         ],
         "8888/tcp": [
             {
                 "HostIp": "",
                 "HostPort": "8888"
             } 
         ]
     }
    

    重新启动您的 Docker,然后它应该可以正常工作。


    2
    在 Docker 版本 17.09.0-ce 上,这对我没起作用。在我启动容器之后,配置文件被覆盖回旧值。 - thegeko
    6
    在主机系统中重启Docker服务。 - yurenchen
    5
    这个有效!谢谢!1.停止容器,2.更改文件,3.重启Docker,4.重新启动容器。 - Dee
    对我来说完美地工作了。感谢您直接提供文件示例。没有您我就做不到。 - RicHincapie
    1
    Windows下WSL2环境中Docker文件的位置:https://dev59.com/d1EG5IYBdhLWcg3weOkT - Vadzim
    这是干净的。谢谢。 - krkart

    26

    不确定是否可以在正在运行的容器上应用端口映射。您可以在运行容器时应用端口转发,这与创建新容器不同。

    $ docker run -p <public_port>:<private_port> -d <image>  
    

    将开始运行容器。这篇教程解释了端口重定向。


    3
    是的,似乎只能在创建容器时设置诸如端口映射之类的选项。 - thasmo
    36
    FYI,这个答案并不完全正确。docker run 创建并启动一个新的容器。其等效于执行 docker create 后立即执行 docker start - user189198

    24

    如何在Docker Desktop(Windows 10 / MacOS)上更改容器的HostPort

    # list all containers
    $ docker ps -a
    $ docker stop docker101tutorial 
    # Use grep to get id of container
    $ docker inspect docker101tutorial | grep -i id
            "Id": "sha256:fff0a4b22d6f3d2eb8d2748b8a8bbc9967ea87199988acee8e86ac70bce9c3eb",
    # run plain ubuntu docker image with shell and change it's namespace to docker host
    # https://dev59.com/RVIH5IYBdhLWcg3wQLVU#60411313
    # https://forums.docker.com/t/the-location-of-images-in-docker-for-windows/19647/4
    $ docker run -it --privileged --pid=host ubuntu nsenter -t 1 -m -u -i sh
    # We want to find out the directory of docker101tutorial container. We are looking for:
    # `"Image":"sha256:fff0a4b22d6f3d2eb8d2748b8a8bbc9967ea87199988acee8e86ac70bce9c3eb"`
    # in /var/lib/docker/containers/*/config.v2.json
    $ find /var/lib/docker/containers/ -name config.v2.json -exec grep -H fff0a4b22d {} \;
    /var/lib/docker/containers/c1eda20b30f058bce9f8ece3b47a21641df5b399770e12ab57416a954d3c8bbf/config.v2.json
    # edit it
    $ vi /var/lib/docker/containers/c1eda20b30f058bce9f8ece3b47a21641df5b399770e12ab57416a954d3c8bbf/hostconfig.json
    
    • 按下 i 进入插入模式。
    • "HostPort":"80" 改为 "HostPort":"8092"
    • 按下 Escape 并输入 :wq。按下 Enter
    • 现在不要启动/停止 docker101tutorial,否则 HostPort 的更改将被还原。
    • 右键单击 Docker Desktop 系统托盘图标并点击重新启动。
    • 在 Docker Desktop 的容器列表中查看您的容器。显示的端口应该更改为 8092
    • 启动您的容器。现在它将映射到主机上的端口 8092

    参考 @holdfenytolvaj 的回答。


    这个方法可行,我试图在Windows 10上创建一个postgres服务,我已经使用-p端口发布创建了容器,但由于某种奇怪的原因它没有发布5432端口。这个方法可行。有任何想法为什么会发生这种情况吗?FYI - 在主机文件中,我添加了以下内容以使其工作:"PortBindings":{"5432/tcp":[{"HostIp":"","HostPort":"5432"}] - NiharGht
    1
    由于某些原因,最新版的Ubuntu上grep命令不识别“--include”选项,但可以使用find命令来实现相同的功能: find /var/lib/docker/containers/ -name config.v2.json -exec grep -H f7828c0aa {} \; - kreigan
    如何启动这个 Debian 容器?无法在命令行中重新启动。 - bodich
    1
    我的两分钱意见是:在Windows 10家庭版上,我尝试了很多次,但Docker Desktop根本没有成功转发端口。最后,我在Windows上安装了一个SSH服务器(这里使用的是Cygwin SSH服务器),并按照melchi的回答中提到的方法打开了一个反向转发隧道。小贴士:在Windows上,你可以打开cmd.exe,并将你的工作站的WSL IP地址(你可以在任务管理器中的以太网标签下找到它,标记为"vEthernet (WSL)")作为容器中SSH的目标地址。 - RAKK

    17

    我们可以使用诸如ssh之类的便利工具轻松地完成这项任务。

    我正在使用ubuntu主机和基于ubuntu的docker镜像。

    1. 在docker内部安装了openssh-client。
    2. 在docker外部(主机)安装了openssh-server服务器。

    当需要映射新端口时,

    在docker内运行以下命令:

    ssh -R8888:localhost:8888 <username>@172.17.0.1
    

    172.17.0.1是docker接口的IP地址 (您可以在主机上运行ifconfig docker0 | grep "inet addr" | cut -f2 -d":" | cut -f1 -d" "来获取此值)。

    这里我将本地8888端口映射回主机的8888端口。您可以根据需要更改端口。

    如果您需要一个以上的端口,则可以终止ssh并添加一个新的-R行以使用新端口。

    我已使用netcat进行了测试。


    非常感谢您的建议,我在尝试了各种方法后,最终决定采纳它。无论我怎么做,都无法连接到Docker Desktop for Windows 10上的Gitlab容器。 - RAKK

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