不知道大家有没有遇到过这种窒息时刻。前段时间我把一个 SpringBoot 小服务写完,本地启动,所有接口全部通,数据库连接也没问题。写完 Dockerfile 构建镜像,启动容器,看容器日志,没有报错,打印出Started Application in xxx seconds,看起来服务妥妥启动完成。

然后我在宿主机访问接口,直接连接超时。容器内部 curl 能通,宿主机访问就不行。我来回折腾了两个多小时,反复改配置、重新构建镜像,一度怀疑是镜像缓存的问题。后来一点点排查,才发现是几个很基础但是极易忽略的细节。

很多人遇到 Docker 访问失败第一反应就是端口映射没写对,但是实际场景远不止端口问题。下面把高频问题一个个复盘出来。

场景一:服务监听 127.0.0.1,容器外部无法访问

这是最高频的坑,很多新手栽在这里。本地开发的时候,服务监听127.0.0.1没问题,本机可以访问。但是在 docker 容器内部,如果程序只绑定 127.0.0.1,代表只允许容器本机回环地址访问,宿主机哪怕做了端口映射,也转发不进去。

SpringBoot 默认配置,如果不指定 server.address,新版本默认绑定 0.0.0.0。但是如果 application.yml 手动写死了 127.0.0.1,容器部署直接凉凉。

错误配置 application.yml

server:
  port: 8080
  address: 127.0.0.1 # 错误写法,容器内外部无法访问

正确配置:

server:
  port: 8080
  address: 0.0.0.0

小技巧:进入容器内部验证,执行下面命令,在容器里面访问自己服务,如果容器内部 curl 可以通,宿主机不通,大概率就是绑定地址问题。

#进入容器
docker exec -it 容器id /bin/bash
curl 127.0.0.1:8080

场景二:Dockerfile EXPOSE 只是声明,不会自动开放端口

不少初学者有一个误区,以为 Dockerfile 写了 EXPOSE,就代表端口可以对外访问。这里要划重点:EXPOSE 仅仅是文档声明,不会实际开放端口,端口映射必须在 docker run 的时候通过‑p 参数指定

错误示例 Dockerfile

FROM openjdk:17-jdk-slim
WORKDIR /app
COPY target/demo‑0.0.1‑SNAPSHOT.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java","‑jar","app.jar"]

构建镜像之后,如果直接docker run 镜像id,没有加‑p,就算写了 EXPOSE,宿主机依旧访问不到。

正确启动命令,做端口映射:

docker run -d -p 8080:8080 --name demo-service demo-image

-p 宿主机端口:容器内部端口,千万不要写反,写反同样访问失败。

场景三:容器内部访问数据库使用localhost

本地开发数据库装在本机,配置写localhost或者127.0.0.1完全没问题。但是放到 docker 容器中,localhost代表容器自身,不是宿主机。这就会出现:服务启动报数据库连接失败,本地跑正常,docker 里面直接报错。

application.yml 错误配置

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/testdb

解决方式:

  1. 如果数据库部署在宿主机,使用宿主机真实内网 IP,不要写localhost
  2. 使用 docker‑compose 编排,通过服务名作为 hostname 互相访问。

docker‑compose 简单示例:

version: '3.8'
services:
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root123
    ports:
      - "3306:3306"
  demo-app:
    image: demo-image
    ports:
      - "8080:8080"
    depends_on:
      - mysql

此时服务连接数据库 url 写成jdbc:mysql://mysql:3306/testdb

场景四:容器防火墙、宿主机防火墙拦截

前面配置全部没问题,依旧访问失败,就要考虑防火墙。 1. 云服务器安全组,没有放开对外端口,宿主机端口虽然映射成功,外网无法进来。 2. 宿主机 firewalld/iptables 防火墙拦截 docker 转发。

排查思路:先在服务器本机 curl 127.0.0.1:8080,如果服务器本机可以访问,但是浏览器外网访问不通,几乎就是云服务器安全组没放行端口。

完整可用的 Dockerfile 示例

FROM openjdk:17-jdk-slim
WORKDIR /app
COPY target/demo-0.0.1-SNAPSHOT.jar app.jar
#EXPOSE仅作为说明
EXPOSE 8080
ENTRYPOINT ["java","-jar","app.jar"]

构建 & 启动命令

#构建镜像
docker build -t demo-app:v1 .
#运行容器,端口映射
docker run -d -p 8080:8080 --name demo demo-app:v1
#查看日志
docker logs -f demo

一套通用排错流程,遇到访问失败按顺序排查

1. 查看容器状态:docker ps确认容器是 Up 状态,如果反复重启,说明程序启动直接报错,看日志定位异常。 2. 进入容器内部docker exec -it 容器id /bin/bash,容器内部 curl 127.0.0.1: 端口,判断容器内部服务是否正常。 3. 确认程序监听地址是不是 0.0.0.0,而不是 127.0.0.1。 4. 确认 docker run 是否正确写‑p 端口映射,端口前后顺序没有颠倒。 5. 确认数据库、redis 等中间件地址,不要写localhost。 6. 服务器本机 curl 测试,区分是本机问题还是外网安全组防火墙问题。

写在最后

本地正常,docker 部署后访问失败,90% 都不是代码业务逻辑 bug,而是环境网络层面的问题。本地开发环境和 docker 容器网络模型完全不一样,很多在本机成立的配置,放到容器中直接失效。

我之前踩坑的时候,一开始总怀疑是代码、jar 包打包问题,反复重新 mvn 打包,浪费大量时间。后面养成固定排错步骤,先区分容器内部能不能访问,再向外一层一层排查,问题定位速度快很多。

另外提醒一点,不要忽略镜像缓存,修改 Dockerfile 之后构建镜像,如果改动不生效,可以加上--no-cache参数构建,避免旧缓存干扰。

更多推荐