跳至主要内容

如何开放 Docker 端口

了解如何在 Docker 中有效地开放与发布端口,使您的容器能够与外部世界通信。指南涵盖从 Dockerfile 配置与运行时标志,到 Docker Compose 编排与故障排查技巧的全部内容。
· 11分钟 阅读

使用 AI 探索

在使用 Docker 容器时,我遇到的最关键问题之一,就是让容器与外部世界进行网络通信。没有正确的端口配置,容器化应用会保持隔离,无法访问。本文将带您完成开放 Docker 端口的全过程——这是一项从事容器化应用工作的人必须掌握的基础技能。

本指南专为需要让其容器化应用对用户、其他服务或外部系统可访问的软件开发者、DevOps 工程师和 IT 专业人士而写。无论您是在容器中部署 Web 服务器、API,还是数据库,理解端口开放对于构建可用的 Docker 系统至关重要。

读完本文,您将清楚理解 Docker 网络的工作原理、开放端口的不同方式,以及如何在自己的项目中实践这些方法。

如果您是 Docker 新手,建议查看我们的课程,例如 Introduction to Docker、Containerization and Virtualization with Docker and Kubernetes、Intermediate Docker 或 Containerization and Virtualization Concepts。

在 Docker 中开放端口是什么?

在深入技术细节之前,很有必要先理解在 Docker 生态中“开放端口”意味着什么。

容器网络隔离原则

Docker 容器将隔离作为核心原则之一。这种隔离通过 Linux 内核特性(如 namespaces 和 cgroups)实现。Namespaces 提供进程隔离,确保容器内的进程无法看到或干扰其他容器或宿主机的进程;而 cgroups 则控制资源分配,限制容器可使用的 CPU、内存等资源。

从网络角度看,每个容器都有自己的网络命名空间,并配有虚拟以太网接口(veth 对)。默认情况下,该接口连接至 Docker 的网桥网络,使容器之间可以通信,同时与宿主机网络保持隔离。您可以将其理解为每个容器都有一个对外不可见的私有网络地址。

在默认配置下,Docker 容器完全处于沙盒环境。容器内运行的服务可以相互通信,但从容器外部(甚至宿主机)无法直接访问。这时就需要进行端口开放。

Expose 与 publish:语义区分

在配置 Docker 端口时,您会遇到两个相关但不同的概念:expose(声明/暴露)与 publish(发布)。

Expose 主要是一个文档化特性。当您在 Docker 中暴露端口时,本质上是在镜像中添加元数据,指明容器化应用会监听哪些端口。

然而,仅仅暴露端口并不会让它从容器外部可达。它只是为镜像使用者提供文档说明。

Publish 才是真正让容器内服务对外可用的方式。发布会在宿主机端口与容器端口之间创建映射。当您发布端口时,Docker 会配置宿主机网络,将指定宿主机端口的流量转发至对应的容器端口。

何时使用哪种方式:

  • 当您构建供其他 Docker 用户使用的镜像时,使用 expose 来记录应用使用的端口。
  • 当需要外部系统(包括宿主机)访问容器内服务时,使用 publish。
  • 在生产环境中,建议两者结合使用,既有完整文档,又具备实际可达性。

如何开放 Docker 端口

理解了概念后,我们来看在不同场景下开放 Docker 端口的实操方法。

Dockerfile 声明策略

最基础的方式是在 Dockerfile 中使用 EXPOSE 指令。它用于声明容器设计要使用的端口。

FROM my-image:latest
EXPOSE 80
EXPOSE 443

在该示例中,我指定容器将使用 80 与 443 端口,分别是 HTTP(超文本传输协议)和 HTTPS(安全超文本传输协议)的标准端口。您也可以合并为一条指令:

EXPOSE 80 443

在指定端口时,如果应用使用特定协议,最好标注协议类型:TCP(传输控制协议)或 UDP(用户数据报协议):

EXPOSE 53/udp
EXPOSE 80/tcp

若未指定协议,默认视为 TCP。这对大多数 Web 应用是合适的,但像 DNS(域名系统)或游戏服务器等服务常常需要 UDP。

从安全角度出发,我建议只暴露应用实际需要的端口。每一个暴露的端口都可能成为潜在攻击面,因此最小化暴露端口数量符合最小权限原则。

运行时端口管理

虽然 EXPOSE 指令用于记录容器使用哪些端口,但要让这些端口能被宿主机或其他机器访问,您需要在运行容器时发布端口。

以下是将容器端口显式绑定到指定宿主机端口的方法:

docker run -p 8081:80 my-image

-p(或 --publish)标志允许您将容器中的单个端口或端口范围映射到宿主机。

该命令将容器内的 80 端口映射到宿主机的 8081 端口。运行后,您可以在浏览器中访问 http://localhost:8081 来访问该 Web 服务器。

为了方便开发,Docker 提供了大写的 -P 或 --publish-all 标志,它会自动将所有暴露的端口发布到宿主机上的随机高位端口:

docker run -P my-image

要查看分配了哪些宿主机端口,我可以使用 docker ps 命令:

docker ps

输出类似如下:

CONTAINER ID   IMAGE  COMMAND    CREATED  STATUS  PORTS                                  NAMES
a7c53d9413bf   image  "/docker-.…"  10 s ago   Up 9 s    0.0.0.0:49153->80/tcp, :::49153->80/tcp   wizardl

这里,容器内的 80 端口被映射到宿主机的 49153 端口。

对于同时在同一端口使用 TCP 与 UDP 的服务(如 DNS 服务器),发布时需指定协议:

docker run -p 53:53/tcp -p 53:53/udp dns-server

Docker Compose 编排

对于多容器应用,Docker Compose 提供了更易管理的网络与端口配置方式。

在 Docker Compose 的 YAML 文件中,您可以在各服务下通过 ports 键指定端口映射:

services:
  web:
    image: my-image
    ports:
      - "8081:80"
      - "443:443"
  api:
    build: ./api
    ports:
      - "3000:3000"

Docker Compose 对 ports 与 expose 做了重要区分。ports 会创建对外可访问的已发布端口映射,而 expose 仅使端口对同一 Compose 网络内的关联服务可用:

services:
  web:
    image: my-image
    ports:
      - "8081:80"
  database:
    image: postgres
    expose:
      - "5432"

在此示例中,web 服务可通过宿主机的 8081 端口访问,而 PostgreSQL 数据库仅对 Compose 文件中的其他服务可见,宿主机无法直接访问。

为了在不同环境下更灵活配置,我可以在端口映射中使用环境变量:

services:
  web:
    image: my-image
    ports:
      - "${WEB_PORT:-8081}:80"

该语法允许我通过环境变量(WEB_PORT)指定宿主机端口,若未设置则默认为 8081。

开放 Docker 端口——完整示例

下面通过一个完整示例演示如何为带数据库后端的 Web 应用开放 Docker 端口。

假设我们在构建一个简单的 Web 应用,包含一个 Python Flask API 服务和一个 PostgreSQL 数据库。API 服务监听 5000 端口,PostgreSQL 使用默认端口 5432。

以下是我们简单的 Flask 应用(app.py)的示例。该应用会创建数据库 myapp,并在数据库中创建好 items 表后,从该表读取数据。

from flask import Flask, jsonify
import os
import psycopg2

app = Flask(__name__)

# Database connection parameters from environment variables
DB_HOST = os.environ.get('DB_HOST', 'db')
DB_NAME = os.environ.get('DB_NAME', 'myapp')
DB_USER = os.environ.get('DB_USER', 'postgres')
DB_PASS = os.environ.get('DB_PASSWORD', 'postgres')

@app.route('/')
def index():
    return jsonify({'message': 'API is running'})

@app.route('/items')
def get_items():
    # Connect to the PostgreSQL database
    conn = psycopg2.connect(
        host=DB_HOST,
        database=DB_NAME,
        user=DB_USER,
        password=DB_PASS
    )
    
    # Create a cursor and execute a query
    cur = conn.cursor()
    cur.execute('SELECT id, name FROM items')
    
    # Fetch results and format as list of dictionaries
    items = [{'id': row[0], 'name': row[1]} for row in cur.fetchall()]
    
    # Close connections
    cur.close()
    conn.close()
    
    return jsonify(items)

if __name__ == '__main__':
    # Run the Flask app, binding to all interfaces (important for Docker)
    app.run(host='0.0.0.0', port=5000)

接着,我会创建 requirements.txt 文件:

flask
psycopg2-binary

现在,我需要为我们的 Python API 服务创建 Dockerfile:

FROM python:3.9-slim
WORKDIR /app

# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy application code
COPY . .

# Expose the port Flask runs on
EXPOSE 5000

# Command to run the application
CMD ["python", "app.py"]

最后,我将创建一个 Docker Compose 文件来编排这两个服务:

services:
  api:
    build:
      context: .
      dockerfile: Dockerfile
      tags:
        - "my-custom-image:latest"
    container_name: my-custom-api
    ports:
      - "5000:5000"
    environment:
      - DB_HOST=db
      - DB_NAME=myapp
      - DB_USER=postgres
      - DB_PASSWORD=postgres
    depends_on:
      - db
  
  db:
    container_name: my-custom-db
    image: postgres:13
    expose:
      - "5432"
    environment:
      - POSTGRES_DB=myapp
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

在该配置中:

  1. API 服务通过我们的 Dockerfile 构建并发布 5000 端口,使其可由宿主机访问。
  2. PostgreSQL 服务仅暴露 5432 端口但未发布,仅对 Compose 网络内的其他服务可见。
  3. API 服务可以使用主机名 db(即服务名)和端口 5432 访问数据库。

运行该应用:

docker-compose up

现在,我可以通过 http://localhost:5000 访问 API,而 PostgreSQL 实例不会直接对 Docker 网络之外暴露——对于数据库服务来说,这是很好的安全实践。但您可以从正在运行的容器中访问 PostgreSQL:

docker compose exec db psql -U postgres -d myapp

该命令将进入 PostgreSQL CLI,在那里我可以查看数据库并创建 items 表。创建完成后,我可以在 http://localhost:5000/items 查看数据。

如何暴露多个 Docker 端口

如果我的 Python 应用需要暴露多个端口,我可以在 Dockerfile 中一次性指定:

FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5000 8081 9090
CMD ["python", "app.py"]

该 Dockerfile 暴露了:

  • 端口 5000:用于主 Flask 应用(当前正在使用)
  • 端口 8081:用于潜在的监控面板
  • 端口 9090:用于未来的调试接口

需要注意的是,尽管我在 Dockerfile 中将 8081 与 9090 暴露出来作为文档说明,但我当前的 Flask 应用只配置为监听 5000 端口。额外暴露的端口用于指示未来可能实现的功能位置。

如何发布已暴露的端口

要发布在 Dockerfile 中暴露的端口,我在运行容器时使用 -p 或 -P 标志:

docker run -p 8081:5000  my-custom-image

这会把容器内(Dockerfile 中已暴露、且 Flask 正在监听的)5000 端口映射到宿主机的 8081 端口。执行后,我可以在浏览器中通过 http://localhost:8081 访问 Flask 应用。

我也可以使用 -P 标志将所有已暴露端口自动发布到宿主机的随机端口:

docker run -P my-custom-image

要检查哪些端口已发布,我可以使用:

docker ps

要获取更详细的信息,我可以使用:

docker port [container_id]
5000/tcp -> 0.0.0.0:32768
8081/tcp -> 0.0.0.0:32769
9090/tcp -> 0.0.0.0:32770

该命令会显示特定容器的所有端口映射。例如,如果我的 Flask 应用只监听 5000 端口,但我在 Dockerfile 中暴露了多个端口,docker port 将精确显示每个容器端口映射到哪个宿主机端口。因此,如果 5000 被映射到了 32768,我就可以通过 http://localhost:32768 访问 Flask 应用。

诊断与故障排查技巧

即使配置周全,Docker 端口映射也可能出现问题。以下是我用来诊断和解决常见问题的一些技巧。

端口映射检查

诊断端口相关问题的第一步,是确认映射是否正确设置。我会使用这些命令:

列出所有带端口映射的正在运行的容器:

docker ps

获取特定容器的详细信息:

docker inspect [container_id]

检查特定容器的端口映射:

docker port [container_id]

docker inspect 命令会提供详细信息,包括网络设置。若只查看网络相关细节,可使用:

docker inspect --format='{{json .NetworkSettings.Ports}}' [container_id] | jq

若需要更深入的流量分析,tcpdump 与 netstat 等工具非常有用。首先确保它们已安装到容器内。如果容器尚未包含这些工具,您可以运行如下命令安装:

docker exec -u root -it f5c6cac71492 bash -c "apt-get update && apt-get install -y net-tools tcpdump"

查看容器内应用正在监听哪些端口:

docker exec -it [container_id] netstat -tuln
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 0.0.0.0:5000            0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.11:42265        0.0.0.0:*               LISTEN
udp        0      0 127.0.0.11:34267        0.0.0.0:*

这表明,例如 Flask 应用在所有网卡的 5000 端口上监听。

要监控容器主网络接口(通常为 eth0)上的进出流量:

docker exec -it [container_id] tcpdump -i eth0
listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes

该命令可用于跟踪流量,判断请求是否到达容器,或数据包是否被丢弃或错误路由。

Iptables 规则分析

Docker 使用 iptables 来管理网络地址转换(NAT)并控制宿主机与容器之间的流量。它会自动插入规则,将宿主机端口转发至相应的容器 IP 与端口,从而实现对容器化服务的无缝访问。

在原生 Linux 宿主机上,您可以使用如下命令查看这些规则:

sudo iptables -t nat -L DOCKER -n -v

这会展示 Docker 如何将特定宿主机端口的入站流量映射到容器端点。基于前面的 Docker Compose 文件,您应能看到被转发的端口:

Chain DOCKER (2 references)
 pkts bytes target     prot opt in     out     source               destination
   50  3000 RETURN     all  --  docker0 *       0.0.0.0/0            0.0.0.0/0
  100  6000 RETURN     all  --  br-xxxxxxx *       0.0.0.0/0            0.0.0.0/0
  500 30000 DNAT       tcp  --  !br-xxxxxxx *       0.0.0.0/0            0.0.0.0/0            tcp dpt:5000 to:172.18.0.2:5000
  400 24000 DNAT       tcp  --  !br-xxxxxxx *       0.0.0.0/0            0.0.0.0/0            tcp dpt:8081 to:172.18.0.2:5000
  300 18000 DNAT       tcp  --  !br-xxxxxxx *       0.0.0.0/0            0.0.0.0/0            tcp dpt:9090 to:172.18.0.2:5000

其中:

  • DNAT 规则将来自宿主机的流量重定向到容器。
  • RETURN 规则确保 Docker 内部网络流量的正确路由。

不过,取决于您的环境(例如 Windows 上的 Docker Desktop 或 WSL2),Docker 可能以不同方式处理端口转发,系统内可能无法看到或修改 iptables 规则。在这种情况下,您可以通过以下命令检查端口转发:

sudo ss -tulnp
Netid    State       Recv-Q      Send-Q            Local Address:Port         Peer Address:Port 
tcp        LISTEN     0                 4096                                          *:5000           	        *:*           
tcp        LISTEN     0                 4096                          		   *:8081                               *:*    
tcp        LISTEN     0                 4096                          		   *:9090                              *:*    

故障排查提示:

  • 使用 docker ps 与 docker port [container] 确认端口映射。
  • 检查宿主机防火墙或安全软件是否阻止了端口。
  • 使用容器日志与 netstat、tcpdump 等网络工具核实应用的网络行为。

结论

开放 Docker 端口是打通容器隔离与实际可用性之间鸿沟的基础技能。本文阐明了 Docker 的网络模型、暴露与发布端口的区别,并给出了多种场景下的实用示例。

请牢记以下要点:

  • 在 Dockerfile 中使用 EXPOSE 记录应用使用的端口
  • 运行容器时使用 -p 或 -P 让这些端口对外可达
  • 使用 Docker Compose 管理复杂的多容器应用
  • 当出现问题时,使用诊断工具进行排查

掌握这些概念与技巧,将使您能够设计更健壮的容器化应用,让其在内部与外部都能高效通信。无论您是在开发简单的 Web 应用,还是构建复杂的微服务架构,正确的端口管理都是成功的关键。

随着容器持续主导现代应用的部署策略,熟练掌握 Docker 网络与端口管理将始终是每位开发者工具箱中的必备技能。

想要继续学习,欢迎查看以下资源:

Exposing a Docker Port FAQs

What does it mean to expose a port in Docker?

在 Docker 中暴露端口是一种记录容器化应用监听哪些端口的方式,但它并不会使该端口在容器外部可访问。

How do I make a Docker container’s port accessible from my host machine?

您可以在运行容器时通过 docker run 的 -p 标志,或在 Docker Compose 的 ports 部分发布端口,从而将宿主机端口映射到容器端口。

What is the difference between `EXPOSE` and `-p` in Docker?

EXPOSE 在 Dockerfile 中用于声明端口以作文档说明,而 -p 则用于发布并映射这些端口以供外部访问。

Can I expose multiple ports for a single Docker container?

可以。您可以在 Dockerfile 中使用多条 EXPOSE 指令暴露多个端口,或在 Docker Compose 的 ports 部分指定多个端口。

How do I troubleshoot Docker port mapping issues?

使用 docker ps、docker port、docker inspect 等命令,以及容器内的网络工具(如 netstat、tcpdump)来验证端口映射和流量情况。

Benito Martin

作者

Benito Martin

作为 Martin Data Solutions 的创始人兼自由职业数据科学家、ML 与 AI 工程师,我在回归、分类、NLP、LLM、RAG、神经网络、集成方法和计算机视觉等领域拥有多元项目经验。

  • 成功交付多项端到端机器学习项目,覆盖数据清洗、分析、建模与在 AWS 和 GCP 上的部署,提供具备影响力且可扩展的解决方案。
  • 使用 Streamlit 和 Gradio 构建交互式、可扩展的网页应用,服务于不同行业场景。
  • 教授并指导数据科学与数据分析学员,通过个性化学习路径促进其职业成长。
  • 为企业级需求设计检索增强生成(RAG)应用课程内容。
  • 撰写高影响力的 AI 与 ML 技术博客,涵盖 MLOps、向量数据库和 LLM 等主题,并获得显著关注。

在我参与的每个项目中,我都会应用最新的软件工程与 DevOps 实践,如 CI/CD、代码规范检查、格式化、模型监控、实验追踪以及健壮的错误处理。我致力于交付完整的解决方案,将数据洞察转化为切实可行的策略,帮助企业成长,并充分发挥数据科学、机器学习与 AI 的价值。

主题