容器显示unhealthy通常因健康检查失败而非服务宕机,需检查HEALTHCHECK命令是否能在容器内执行成功、应用是否监听0.0.0.0、启动缓冲期是否足够及健康接口是否足够轻量。

容器在 Docker Desktop 里显示 unhealthy,大概率不是服务挂了,而是健康检查本身没跑通——先别删容器、别重build,重点查 HEALTHCHECK 命令在容器内能不能真正执行成功。
healthcheck 命令在容器里根本找不到可执行文件
最常见的情况:你写了 curl -f http://localhost:8080/health,但基础镜像是 alpine 或精简版 scratch,压根没装 curl。命令一执行就报 /bin/sh: curl: not found,退出码非 0,Docker 直接判 unhealthy。
- 进容器确认:
docker exec -it <container_id> /bin/sh,然后手动运行健康检查命令,看是否报“command not found” - Alpine 镜像可用
wget替代(默认自带):CMD wget --spider -q http://localhost:8080/health || exit 1 - 或改用更底层的
nc(需apk add --no-cache netcat-openbsd):CMD nc -z localhost 8080 || exit 1 - 极简镜像(如
scratch)建议直接暴露 TCP 端口健康检查,避免依赖 shell 工具
应用监听地址不对,localhost 连不上自己
健康检查命令运行在容器网络命名空间内,“localhost” 指向容器自身,但如果你的应用只绑定了 127.0.0.1:8080,而没监听 0.0.0.0:8080,那从容器内部用 curl http://localhost:8080 也会被拒绝连接(Connection refused)。
- 检查应用启动日志,确认监听地址是
0.0.0.0而非127.0.0.1(例如 Go 的http.ListenAndServe("0.0.0.0:8080", ...)) - 临时验证:进容器后执行
netstat -tuln | grep :8080,看 LISTEN 地址是不是*:8080或0.0.0.0:8080 - 不要改成
http://host.docker.internal——那是宿主机视角,健康检查必须走容器内 localhost
健康检查参数和应用实际启动节奏对不上
Docker 默认的 --start-period=0s 意味着容器一启动就立刻开始健康检查。但很多应用(尤其带数据库迁移、缓存预热的)要 20–60 秒才真正 ready,结果检查全失败,直接累积到 unhealthy。
- 必须显式设置
--start-period,给足启动缓冲时间,比如--start-period=60s -
--timeout别设太小:HTTP 接口哪怕只是返回 OK,也可能因 GC 或调度延迟卡住,建议 ≥5s(别信默认的 3s) -
--interval别贪快:每 10 秒检查一次在多数场景纯属扰动,30s 是更稳妥的起点 - 在
docker inspect <container_id>输出里重点看.State.Health.Log,它会记录每次检查的命令、退出码、耗时和时间戳——这是唯一能确认“到底是超时还是连不上还是命令不存在”的证据
用了 HTTP 健康检查,但接口本身不轻量
如果 /health 端点内部做了数据库查询、远程调用或复杂计算,它本身就可能超时或失败。一次外部网络抖动,就会让整个容器被标记为 unhealthy,但这不代表你的服务不可用。
- 健康检查应只反映“进程存活 + 端口可连 + 内部状态无崩溃”,不反映下游依赖——把 DB 检查挪到
/ready,/health留给最轻量的响应 - Go 示例中,
/health只写w.WriteHeader(200),不碰任何外部资源;/ready才查 DB 连接池 - 若实在没法拆,至少确保健康检查接口有独立超时控制(比如用
context.WithTimeout),别让一次 DB hang 拖垮整个健康判断
真正卡住人的,往往不是配置写错了,而是没去看 docker inspect 里 .State.Health.Log 的原始记录——它不会说谎,但得你主动翻出来看。


















