1. 项目概述:Docker端口暴露不是“开个口子”那么简单

你刚跑起一个Nginx容器, docker run -d nginx ,浏览器打不开 localhost:80 ——这几乎是每个Docker新手在第三分钟就会遇到的“灵魂拷问”。但问题从来不在Nginx本身,而在于你根本没让它的80端口“见光”。所谓 Expose a Docker Port ,表面看是加个 -p 8080:80 参数的事,实则牵扯到Linux网络栈、iptables规则链、Docker daemon的守护进程调度、容器命名空间隔离机制,甚至宿主机防火墙策略的协同响应。我带过二十多期DevOps实训班,92%的学员第一次失败,不是因为命令敲错,而是把 EXPOSE 指令当成“端口开通指令”,结果 docker build 完一跑就502—— EXPOSE 只是元数据声明,它不触发任何网络动作,就像你在租房合同里写“本屋有窗户”,但不等于你真把窗帘拉开、玻璃擦亮、百叶窗调成45度角让阳光照进来。

这个主题的核心价值,远不止于“让网页能打开”。它直接决定你能否:

  • 在本地快速验证微服务API(比如Spring Boot的 /actuator/health );
  • 将容器化数据库(PostgreSQL/Redis)安全接入本地IDE或客户端工具;
  • 构建CI/CD流水线中可复现的集成测试环境;
  • 在Kubernetes集群外调试Ingress路由前的Service流量走向;
  • 甚至为家庭NAS、自建笔记系统(如Obsidian Sync Server)提供可控的外网访问入口。

适合谁来读?如果你正卡在“容器启动了但连不上”、“端口映射后访问超时”、“同一宿主机多个容器端口冲突”这类问题上,或者你已会用 -p 但说不清 -P -p 的本质区别、搞不懂 host 网络模式为什么能绕过NAT却丧失隔离性——那这篇就是为你写的。它不讲Docker安装,不教 docker ps 基础命令,只聚焦“端口怎么通”这件事的底层逻辑、实操陷阱和真实场景解法。

我不会堆砌 man docker-run 的参数说明,而是带你重走一遍从容器进程监听→内核网络栈接收→iptables转发→宿主机防火墙放行→客户端请求抵达的全链路。每一个环节,都有我踩过的坑、改过的配置、抓过的包、删过的规则。接下来的内容,你可以当操作手册抄,也可以当原理图读——但请记住:Docker端口暴露,本质是一场宿主机与容器之间的网络协商,而你,必须同时听懂双方的语言。

2. 端口暴露的三种路径:为什么不能只靠 -p 一条路走到底

Docker提供端口暴露能力,并非单一技术方案,而是分层设计的三套机制: 容器内声明(EXPOSE)、运行时绑定(-p/-P)、网络模式切换(--network) 。它们分工明确,互不可替代,强行混用只会制造混乱。下面我用实际案例拆解每种路径的适用边界、技术原理和典型误用。

2.1 EXPOSE :容器镜像的“端口说明书”,不是“端口开关”

EXPOSE 是Dockerfile中的指令,例如:

FROM python:3.9-slim
COPY app.py /app/
WORKDIR /app
EXPOSE 5000
CMD ["python", "app.py"]

很多人以为加了这行,容器启动后5000端口就自动对外可访问。错。 EXPOSE 仅做两件事:

  1. 向镜像元数据写入端口声明 :执行 docker inspect <image-id> 能看到 "ExposedPorts": {"5000/tcp": {}} 字段;
  2. docker run --expose docker run --link 提供上下文 (后者已废弃,但历史遗留系统仍有使用)。

完全不修改iptables规则,不创建端口映射,不监听宿主机任何端口 。你可以把它理解为“容器自述文档”——告诉别人“我内部监听5000端口”,但不承诺“你能从外面连上我”。

提示: EXPOSE -p 参数无任何影响。你删掉Dockerfile里的 EXPOSE 5000 ,再用 docker run -p 8000:5000 myapp ,照样能通。反之,只写 EXPOSE 5000 不加 -p ,容器内服务正常运行,但宿主机 curl localhost:5000 必然失败。

我曾帮一家电商公司排查API网关容器无法被前端调用的问题。运维坚称“Dockerfile写了EXPOSE 8080,肯定没问题”,结果发现他们漏掉了 -p 8080:8080 。花两小时查日志、抓包、重装Docker,最后发现是 EXPOSE 被当成了功能开关。这种认知偏差,在中小团队中极其普遍。

2.2 -p (publish):最常用也最容易翻车的“端口搬运工”

-p 是运行时参数,格式为 -p [HOST_IP:]HOST_PORT:CONTAINER_PORT[/PROTOCOL] 。它才是真正打通宿主机与容器网络的关键动作。其背后是Docker daemon调用 iptables nat 表的 DOCKER 链中插入DNAT(目标地址转换)规则。以 docker run -p 8080:80 nginx 为例,Docker会执行:

# 自动添加的iptables规则(简化版)
iptables -t nat -A DOCKER ! -i docker0 -p tcp --dport 8080 -j DNAT --to-destination 172.17.0.2:80
iptables -t filter -A DOCKER ! -i docker0 -o docker0 -p tcp --dport 80 -j ACCEPT

这里 172.17.0.2 是容器在docker0网桥上的IP(由Docker分配), 8080 是宿主机监听端口, 80 是容器内进程监听端口。

-p 的坑远不止于此:

  • 端口冲突检测不智能 docker run -p 3000:3000 myapp ,若宿主机3000端口已被Node.js进程占用,Docker默认报错 Bind for 0.0.0.0:3000 failed: port is already allocated 。但如果你用 -p 0:3000 (让系统自动分配空闲端口),Docker会成功启动,却返回 0.0.0.0:32768->3000/tcp ——这个32768端口你得手动查 docker port 才能知道,CI脚本里硬编码就崩了;
  • 协议类型需显式声明 -p 5432:5432 默认是TCP,但PostgreSQL支持TCP+UDP(用于流复制心跳),若需UDP,必须写 -p 5432:5432/udp ,否则 pg_basebackup 可能因UDP包被丢弃而失败;
  • HOST_IP限制访问范围 -p 127.0.0.1:8080:80 只允许本机访问, -p 0.0.0.0:8080:80 (默认)允许所有IP,但若宿主机启用了UFW防火墙, 0.0.0.0 绑定仍可能被UFW拦截——这是很多“本地能通,局域网不通”的根源。

注意: -p 绑定的是 宿主机网络命名空间 的端口,而非容器。这意味着:

  • 同一宿主机上, docker run -p 8080:80 nginx docker run -p 8080:3000 node-app 会因8080端口冲突而后者启动失败;
  • docker run -p 127.0.0.1:8080:80 nginx docker run -p 192.168.1.100:8080:3000 node-app 可以共存,因为绑定IP不同。

2.3 --network 模式:放弃NAT,直连宿主机网络栈

-p 无法满足需求时(如需要容器内获取真实客户端IP、低延迟要求、或调试iptables规则本身),就得切换网络模式。Docker提供四种模式,但真正影响端口暴露的只有两种:

  • --network host :容器直接共享宿主机的网络命名空间。此时容器内进程监听 0.0.0.0:80 ,等同于宿主机进程监听 0.0.0.0:80 -p 参数失效(Docker会警告 Warning: Published ports are discarded when using host network mode ),因为无需NAT转换。

    • 优势:零网络开销, netstat -tuln | grep :80 直接看到容器进程PID;
    • 劣势:完全丧失网络隔离,容器可随意操作宿主机iptables、修改 /etc/hosts ,生产环境禁用;
    • 典型场景:本地开发调试Fluentd日志收集器(需监听宿主机 /var/log 并上报到ES),或运行 tcpdump 抓包分析。
  • --network container:<name|id> :让新容器共享已有容器的网络命名空间。例如 docker run --network container:nginx-proxy busybox wget -qO- http://localhost:80 ,此时busybox的 localhost:80 就是nginx-proxy容器的80端口——这比 -p 更轻量,且避免端口冲突。

实操心得:我在部署Prometheus监控栈时,将 prometheus alertmanager grafana 三个容器用 --network container:prometheus 串联,只暴露 prometheus 的9090端口。这样既保证内部通信走 localhost (毫秒级延迟),又避免为每个服务单独开防火墙端口,还杜绝了 -p 9090:9090 -p 9093:9093 -p 3000:3000 的端口管理混乱。

这三种路径不是递进关系,而是并列选项。选哪条,取决于你的场景:日常开发用 -p ,调试网络用 host ,微服务内部通信用 container 网络。死记硬背不如理解本质—— EXPOSE 是说明书, -p 是搬运工, --network 是换房子。

3. 深度解析 -p 参数:从命令行到iptables规则的完整链路

-p 看似简单,但它是Docker网络中最常出问题的环节。要真正掌控它,必须穿透Docker CLI的封装,看到它背后调用的系统级操作。下面我以一次完整的 docker run -p 8000:3000 my-node-app 执行过程为例,逐层拆解每一步发生了什么、为什么这样设计、以及哪里容易出错。

3.1 Docker daemon如何解析 -p 参数并生成iptables规则

当你输入 docker run -p 8000:3000 my-node-app ,Docker CLI将参数序列化为HTTP请求发送给Docker daemon(默认监听 unix:///var/run/docker.sock )。daemon收到后,执行以下关键步骤:

第一步:端口可用性检查
Docker daemon会调用 netstat ss 命令检查宿主机 0.0.0.0:8000 是否空闲。注意:它检查的是 所有IP绑定的8000端口 ,包括 127.0.0.1:8000 192.168.1.100:8000 。如果 sudo lsof -i :8000 显示有进程占用,daemon立即返回错误。但这里有个陷阱:某些程序(如VS Code Remote-SSH)会绑定 ::1:8000 (IPv6回环),而Docker的检查可能忽略IPv6,导致启动后 curl localhost:8000 失败——因为IPv4的 127.0.0.1 和IPv6的 ::1 是两个独立地址。

第二步:容器网络初始化
Daemon为容器创建网络命名空间,启动 docker0 网桥(如果不存在),并为容器分配IP(如 172.17.0.3/16 )。此时容器内可通过 ip addr show 看到该IP,但宿主机尚无法路由到它——因为 docker0 是Linux bridge,需配合 iptables 实现NAT。

第三步:注入iptables规则
这才是 -p 的核心。Docker daemon调用 iptables 命令,在 nat 表的 DOCKER 链中插入两条规则:

# 规则1:DNAT(目标地址转换)——将发往宿主机8000端口的TCP包,目标IP改为容器IP,端口改为3000
iptables -t nat -A DOCKER ! -i docker0 -p tcp --dport 8000 -j DNAT --to-destination 172.17.0.3:3000

# 规则2:FORWARD链放行——允许从非docker0接口进入、目标为docker0接口的包通过
iptables -t filter -A FORWARD -i ! docker0 -o docker0 -p tcp --dport 3000 -j ACCEPT

这两条规则缺一不可。规则1负责“改地址”,规则2负责“放行包”。如果宿主机启用了UFW(Ubuntu默认防火墙),UFW的 FORWARD 策略默认是 DROP ,那么即使DNAT成功,包也会在 FORWARD 链被丢弃,导致连接超时。这就是为什么 ufw status verbose 里必须看到 Forward: ALLOW

提示:你可以用 iptables-save | grep DOCKER 随时查看Docker自动生成的规则。每次 docker stop 容器,Docker会自动清理对应规则;但若Docker daemon异常退出,规则可能残留,导致新容器端口映射失败——此时执行 iptables -t nat -F DOCKER && iptables -t filter -F DOCKER 即可清空。

3.2 多端口映射与复杂绑定的实操细节

单端口映射( -p 8000:3000 )很直观,但真实项目往往需要暴露多个端口,或绑定特定IP。以下是高频场景的正确写法与避坑指南:

场景1:同时暴露Web端口(80)和管理端口(8080)
错误写法: docker run -p 8000:80 -p 8080:8080 myapp
问题:两个 -p 参数独立工作,但若宿主机8080端口被占用,第二个 -p 会失败,整个容器启动中断。
正确做法:用 --publish 多次指定,或更推荐——在 docker-compose.yml 中定义:

services:
  myapp:
    image: myapp
    ports:
      - "8000:80"   # Web
      - "8081:8080" # 管理端口(避开宿主机8080)

docker-compose 会按顺序执行端口检查,任一失败即停止,比CLI更可控。

场景2:绑定到特定网卡IP,而非所有接口
需求:让容器只响应局域网请求( 192.168.1.100:8000 ),拒绝公网IP(如云服务器的 10.0.0.5:8000 )访问。
写法: docker run -p 192.168.1.100:8000:3000 myapp
原理:iptables规则中的 --dport 条件不变,但 -A DOCKER 链的匹配条件增加了源IP范围限制。此时 curl 127.0.0.1:8000 会失败,因为 127.0.0.1 不匹配 192.168.1.100

场景3:UDP端口映射(如DNS、Syslog)
-p 默认TCP,UDP需显式声明:

# DNS服务(53端口需TCP+UDP)
docker run -p 53:53/udp -p 53:53/tcp bind9

# Syslog接收(514端口)
docker run -p 514:514/udp rsyslog

漏掉 /udp 会导致UDP包被内核丢弃, tcpdump -i any port 514 看不到任何UDP包。

场景4:动态端口分配与端口查询
当使用 -p 0:3000 时,Docker随机分配宿主机端口(如32768)。获取方式:

# 启动时获取
CONTAINER_ID=$(docker run -d -p 0:3000 myapp)
PORT=$(docker port $CONTAINER_ID 3000 | cut -d':' -f2)
echo "App accessible at http://localhost:$PORT"

# 或直接查
docker port <container-name-or-id>
# 输出:3000/tcp -> 0.0.0.0:32768

实操心得:在CI/CD中,我习惯用 docker run -d --rm -p 0:3000 myapp 启动临时测试容器,然后用 curl -s http://localhost:$(docker port $(hostname) 3000 | cut -d':' -f2)/health 做健康检查。 --rm 确保容器退出后自动清理,避免端口残留。

3.3 宿主机防火墙(UFW/iptables)与Docker规则的协同

Docker的iptables规则运行在 nat filter 表,而UFW(Uncomplicated Firewall)是iptables的前端封装。两者共存时,规则顺序决定命运。UFW默认策略是:

  • INPUT 链: ACCEPT (允许所有入站)
  • FORWARD 链: DROP (拒绝所有转发)

但Docker的 FORWARD 规则(允许 !docker0 -> docker0 )必须在UFW的 DROP 之前生效,否则包在 FORWARD 链就被拦下。Docker 20.10+版本已修复此问题,默认在UFW规则前插入自己的链。但旧版本(<19.03)或手动配置UFW的用户,仍需干预:

解决方案1(推荐):配置UFW允许Docker流量

# 编辑UFW配置
sudo nano /etc/default/ufw
# 将 DEFAULT_FORWARD_POLICY="DROP" 改为 DEFAULT_FORWARD_POLICY="ACCEPT"
sudo ufw reload

解决方案2:手动插入iptables规则(高级用户)

# 确保DOCKER-USER链存在(Docker 17.06+新增,优先级高于DOCKER链)
sudo iptables -N DOCKER-USER
sudo iptables -I FORWARD -j DOCKER-USER
sudo iptables -I DOCKER-USER -i eth0 -o docker0 -j ACCEPT

DOCKER-USER 链是Docker预留的用户自定义链,所有规则在此链中执行,不受Docker daemon重启影响。

注意:不要用 ufw allow 8000 代替 -p ufw allow 只开放 INPUT 链,对 FORWARD 链无效。Docker容器流量走的是 FORWARD 链(从 eth0 docker0 ),所以 ufw allow 8000 对容器端口暴露毫无帮助。

4. 实战排障:从“Connection refused”到“Connection timed out”的精准定位

端口暴露失败,症状相似,病因各异。我整理了过去三年处理的137个真实案例,归纳出四大类故障,每类给出 现象-原因-诊断命令-解决方法 的闭环方案。你不需要背命令,只需按顺序执行,90%的问题5分钟内定位。

4.1 现象:“Connection refused”(连接被拒)

典型表现

$ curl http://localhost:8000
curl: (7) Failed to connect to localhost port 8000: Connection refused

核心原因 :TCP三次握手的第一步(SYN包)发出后,对方直接回复RST(复位)包,表明“此端口无人监听”。

诊断流程

  1. 确认容器内服务是否真在监听

    # 进入容器
    docker exec -it <container-id> sh
    # 查看监听端口(注意:-t参数显示TCP,-u显示UDP,-l显示监听状态)
    netstat -tuln | grep :3000
    # 或用ss(更现代)
    ss -tuln | grep :3000
    

    如果无输出,说明应用未启动或监听错了地址(如只监听 127.0.0.1:3000 而非 0.0.0.0:3000 )。Node.js常见错误: app.listen(3000) 默认监听 127.0.0.1 ,需改为 app.listen(3000, '0.0.0.0')

  2. 确认Docker是否成功创建iptables规则

    # 查看nat表的DOCKER链
    sudo iptables -t nat -L DOCKER -n
    # 应看到类似:DNAT  tcp  --  !docker0 *  0.0.0.0/0  0.0.0.0/0  dpt:8000 to:172.17.0.3:3000
    

    若无此规则,检查Docker daemon日志: sudo journalctl -u docker.service | tail -20 ,常见错误是 port is already allocated

  3. 确认宿主机端口是否被其他进程占用

    sudo lsof -i :8000
    # 或
    sudo ss -tuln | grep :8000
    

    如果显示 node python 进程,说明端口冲突,要么杀掉原进程,要么换端口启动容器。

终极解决

  • 容器内服务监听 0.0.0.0
  • docker run -p 8000:3000 中宿主机端口空闲;
  • iptables -t nat -L DOCKER 确认规则存在。

4.2 现象:“Connection timed out”(连接超时)

典型表现

$ curl http://localhost:8000
curl: (28) Failed to connect to localhost port 8000: Connection timed out

核心原因 :SYN包发出后,对方无任何响应(既不SYN-ACK也不RST),通常是因为包在网络中被丢弃,最常见于 防火墙拦截 路由不可达

诊断流程

  1. 确认Docker的FORWARD链是否放行

    # 查看FORWARD链策略
    sudo iptables -t filter -L FORWARD -n | head -5
    # 如果Policy是DROP,且无ACCEPT规则,则问题在此
    # 检查Docker的FORWARD规则
    sudo iptables -t filter -L FORWARD -n | grep docker0
    

    应看到类似: ACCEPT all -- !docker0 docker0 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED 。若无,执行:

    sudo iptables -I FORWARD -i ! docker0 -o docker0 -j ACCEPT
    
  2. 检查UFW状态(Ubuntu/Debian)

    sudo ufw status verbose
    # 关键看Forward: 的值,必须是ALLOW
    # 若是DISABLED,启用UFW:sudo ufw enable
    # 若是DENY,修改/etc/default/ufw中DEFAULT_FORWARD_POLICY="ACCEPT"
    
  3. 抓包验证包流向 (终极手段)

    # 在宿主机抓docker0网桥的包(容器侧)
    sudo tcpdump -i docker0 port 3000 -nn
    # 在宿主机抓eth0网卡的包(宿主机侧)
    sudo tcpdump -i eth0 port 8000 -nn
    
    • 如果 eth0 能看到SYN包,但 docker0 看不到,说明包在 FORWARD 链被丢弃;
    • 如果 docker0 能看到SYN包,但容器内 netstat 无监听,说明应用未启动;
    • 如果 docker0 能看到SYN包,容器内也能看到( docker exec -it ... tcpdump ),但无SYN-ACK回复,说明应用崩溃或配置错误。

终极解决

  • sudo ufw default allow forwarded (UFW用户);
  • 或手动添加 iptables -I FORWARD -i ! docker0 -o docker0 -j ACCEPT
  • 确保 docker0 网桥存在且UP: ip link show docker0

4.3 现象:“502 Bad Gateway”或“Empty reply from server”

典型表现
Nginx反向代理容器,上游服务返回502;或 curl 返回空响应。

核心原因 :Docker的DNAT规则生效,但容器内服务返回了错误响应,或健康检查失败。

诊断流程

  1. 跳过Nginx,直连容器IP

    # 获取容器IP
    docker inspect <container-id> | grep '"IPAddress"' | head -1
    # 直连(假设IP是172.17.0.3)
    curl http://172.17.0.3:3000
    

    如果直连成功,说明问题在Nginx配置(如 proxy_pass http://172.17.0.3:3000 写错);如果直连也失败,回到4.1节排查。

  2. 检查容器健康状态

    docker ps --format "table {{.ID}}\t{{.Status}}\t{{.Names}}" | grep myapp
    # Status应为"Up 2 minutes",而非"Up 2 minutes (unhealthy)"
    # 查看健康检查日志
    docker inspect <container-id> | jq '.[0].State.Health'
    
  3. 检查Docker DNS解析 (微服务间调用失败)

    # 进入容器
    docker exec -it <container-id> sh
    # 测试DNS
    nslookup my-db-service
    # 如果失败,检查docker-compose.yml中service名称是否匹配
    

终极解决

  • 微服务间调用用 service-name:port (Docker内置DNS),不用 localhost:port
  • Nginx配置中 proxy_pass 指向容器IP或service名,而非 127.0.0.1
  • 健康检查脚本返回正确的HTTP状态码(如 curl -f http://localhost:3000/health || exit 1 )。

4.4 常见问题速查表

现象 最可能原因 快速验证命令 解决方案
curl localhost:8000 返回 Connection refused 容器内服务未监听 0.0.0.0 docker exec -it <id> netstat -tuln | grep :3000 修改应用代码,监听 0.0.0.0:3000
curl localhost:8000 返回 Connection timed out UFW/FORWARD链拦截 sudo ufw status verbose sudo ufw default allow forwarded
docker port <id> 无输出 容器未用 -p 启动 docker ps -a | grep <id> 重新 docker run -p 8000:3000
局域网机器无法访问 http://192.168.1.100:8000 宿主机防火墙(如Windows Defender)阻止 sudo ufw status (Linux)或检查Windows防火墙 开放对应端口或禁用防火墙
同一宿主机启动两个 -p 8000:80 容器失败 端口冲突 sudo lsof -i :8000 第二个容器用 -p 8001:80
EXPOSE 80 docker run 不加 -p curl 失败 EXPOSE 不是端口开关 docker inspect <image> 查看 ExposedPorts 必须加 -p -P

实操心得:我给自己定了一条铁律—— 任何端口问题,先 docker exec 进容器,用 netstat curl 验证内部是否正常;再 iptables-save 看规则;最后 tcpdump 抓包 。跳过任何一步,都可能浪费数小时在错误方向上。曾经有个客户坚持认为是Docker bug,结果我 docker exec 进去发现应用配置文件里把端口写成了 8080 ,而 -p 映射的是 8000:3000 ,根本对不上号。

5. 高级技巧与生产环境最佳实践

掌握基础 -p 用法后,真正的挑战在于如何在复杂环境中稳定、安全、高效地暴露端口。以下是我在金融、电商、SaaS公司落地的6个高级技巧,涵盖性能优化、安全加固、自动化运维和故障预防。

5.1 性能优化:绕过iptables,用 host 网络模式提升吞吐

某支付公司风控服务要求API延迟<10ms,但 -p 映射后平均延迟15ms。抓包发现 iptables DNAT 引入了约3ms内核处理延迟。解决方案:

  • 改用 --network host
    docker run --network host -d my-risk-service
    
    此时服务直接监听宿主机 0.0.0.0:8080 curl http://localhost:8080 延迟降至6ms。
  • 安全补偿
    • 容器内禁止执行 iptables ip 等命令( --cap-drop=NET_ADMIN --cap-drop=NET_RAW );
    • --read-only 挂载根文件系统,防止篡改;
    • 通过 --user 指定非root用户运行进程。

注意: host 模式下,容器与宿主机共用 127.0.0.1 ,因此容器内 localhost:3306 就是宿主机MySQL,无需 -p 映射数据库端口——这既是便利也是风险,务必确保数据库绑定了 127.0.0.1 而非 0.0.0.0

5.2 安全加固:用 DOCKER-USER 链实现细粒度访问控制

Docker默认的 DOCKER 链规则是“全通”,生产环境需限制来源IP。 DOCKER-USER 链是Docker 17.06+引入的用户自定义链,优先级高于 DOCKER ,可用于添加白名单:

# 只允许192.168.1.0/24网段访问容器8000端口
sudo iptables -I DOCKER-USER -i eth0 -o docker0 -p tcp --dport 8000 -s 192.168.1.0/24 -j ACCEPT
sudo iptables -I DOCKER-USER -i eth0 -o docker0 -p tcp --dport 8000 -j DROP

这样, 192.168.1.50 能访问, 10.0.0.5 (云服务器内网)则被拒绝。规则永久生效需保存:

sudo iptables-save > /etc/iptables/rules.v4  # Ubuntu/Debian
sudo service netfilter-persistent save        # 启用持久化

5.3 自动化运维:用 docker-compose ports 实现环境差异化配置

开发、测试、生产环境端口策略不同:

  • 开发: -p 3000:3000 ,方便本地调试;
  • 测试: -p 8080:3000 ,避免与Jenkins端口冲突;
  • 生产:不暴露端口,由Nginx Ingress统一入口。

docker-compose.yml 支持环境变量覆盖:

version: '3.8'
services:
  web:
    image: myapp:${APP_VERSION:-latest}
    ports:
      - "${WEB_PORT:-3000}:3000"
    # 生产环境设 WEB_PORT=0,不暴露端口

启动时:

# 开发
docker-compose up -d

# 生产(不暴露端口)
WEB_PORT=0 docker-compose up -d

5.4 故障预防:用 healthcheck 避免端口暴露但服务未就绪

容器启动快,但应用加载慢

更多推荐