Docker 容器启动失败怎么查?端口、日志、权限与网络排障完整教程

容器起不来,最怕的不是报错,而是你不知道该从哪里下手。尤其是本地明明写好了 docker run 或 docker compose up,结果页面打不开、端口没响应、日志刷一堆英文,越改越乱。
这篇就按一次真实排障的顺序来走:先确认容器状态,再看日志,再查端口、权限、环境变量和网络。服务恢复后,再用 cpolar 做一次公网访问验证,确认不是"本机能开,别人打不开"。
本文适合 Docker 初学者、后端开发,以及需要把本地容器服务临时给同事验收的人。命令都尽量写成可直接复制的形式,容器名统一用 demo-web,示例服务端口统一用 8080。
1 什么是 Docker 容器启动失败?先别急着重装
很多人说"容器启动失败",其实背后有几种情况:
- 容器根本没创建成功
- 容器创建了,但启动后马上退出
- 容器还在运行,但服务端口没有暴露出来
- 容器内服务正常,本机端口或网络链路有问题
这几类问题看起来都像"打不开",排查方式完全不一样。
这里先别急着删容器、重拉镜像。重装能掩盖问题,但排障时最重要的是保留现场:容器状态、启动参数、日志、端口映射、挂载目录,这些信息都能直接指向原因。

这张图后续可以放 docker ps -a 的结果。读者看到 Exited、Restarting、Up 这几种状态,就能判断下一步该看哪里。
2 环境准备:确认 Docker 可用并准备一个排查对象
正式查问题前,先确认 Docker 命令本身能正常调用。这里不是为了测试而测试,而是避免把 Docker Desktop、Docker Engine 没启动的问题误判成容器问题。
bash
docker version
docker info
如果 docker version 能输出 Client 和 Server 信息,说明客户端已经能连上 Docker 服务。若只看到 Client,没有 Server,优先检查 Docker Desktop 或 Docker daemon 是否启动。
再看当前容器列表:
bash
docker ps
docker ps -a
docker ps 只看运行中的容器,docker ps -a 会把停止、退出、创建失败后残留的容器也列出来。排障时一定要用 -a,否则你会误以为容器没创建过。
为了后面命令统一,先把要排查的容器名记下来。假设容器名是 demo-web:
bash
docker ps -a --filter "name=demo-web"
提醒一下:容器名不是镜像名。nginx:latest 是镜像,demo-web 才是容器名。这里别填错,不然后面的 logs、inspect 查到的就不是同一个对象。
3 查看容器状态:先判断它是没起来,还是起来又退了
排查 Docker 启动失败,第一眼看 STATUS。
bash
docker ps -a --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"
常见状态可以这样判断:
Up:容器进程还在,重点查端口、应用监听地址、网络Exited:容器已经退出,重点查日志、启动命令、环境变量、权限Restarting:容器反复重启,重点查应用崩溃、健康检查、依赖服务Created:容器创建了但没启动,重点查启动命令或手动执行docker start
如果看到 Exited,不要马上 docker rm。先把退出原因和日志拿出来:
bash
docker inspect demo-web --format '{{.State.Status}} {{.State.ExitCode}} {{.State.Error}}'
docker logs --tail 100 demo-web
ExitCode 是很关键的线索。0 常见于主进程正常结束,比如容器里跑了一个一次性脚本;非 0 则说明启动过程出错。docker logs --tail 100 只取末尾 100 行,排障时更清爽。
如果日志太快刷屏,用下面的命令看带时间戳的最近日志:
bash
docker logs --tail 200 --timestamps demo-web
血泪教训:不要一上来就 docker logs -f 盯半天。先看尾部日志,找到错误关键词,再决定要不要跟随输出。
4 查日志:把错误从"看不懂"拆成三类
日志不是越多越好,关键是把报错归类。Docker 容器启动失败,日志里常见三类问题:配置错、依赖连不上、权限不够。
4.1 配置错:环境变量、启动参数、配置文件路径
如果日志里出现 missing environment variable、config not found、invalid option、no such file 这类信息,先查容器启动时到底传了什么参数。
bash
docker inspect demo-web --format '{{json .Config.Env}}'
docker inspect demo-web --format '{{json .Config.Cmd}}'
docker inspect demo-web --format '{{json .Config.Entrypoint}}'
这一步做完,你能看到容器内实际拿到的环境变量、启动命令和入口命令。很多问题不是镜像坏了,而是启动参数漏了一个 APP_PORT、DATABASE_URL 或配置文件路径写错。
如果你是用 Compose 启动,也可以先检查最终合并后的配置:
bash
docker compose config
这里很实用,尤其是 .env、docker-compose.yml、覆盖文件一起使用时。命令输出的是 Docker Compose 解析后的最终配置,比肉眼翻文件靠谱。
4.2 依赖连不上:数据库、Redis、上游接口
如果日志里有 connection refused、timeout、could not connect,不要只盯应用容器。它启动失败,经常是数据库、Redis、消息队列没准备好。
先看同一项目里的容器状态:
bash
docker ps -a
如果依赖容器也在 Exited,先修依赖。应用容器反复重启,只是后果,不是源头。
容器还在运行时,可以进容器里做最小验证:
bash
docker exec -it demo-web sh
进入后再按镜像内已有工具检查,例如:
bash
printenv
提醒一下:不同镜像内置工具不一样,Alpine、Debian、Distroless 的可用命令差异很大。排障文章里不要假设每个容器都有 curl、ping、netstat,先用 printenv、查看配置路径这种通用动作更稳。
4.3 权限不够:挂载目录、用户 UID、只读文件系统
如果日志里出现 permission denied、read-only file system、operation not permitted,重点查挂载目录和容器运行用户。
先看挂载信息:
bash
docker inspect demo-web --format '{{json .Mounts}}'
再看容器配置的运行用户:
bash
docker inspect demo-web --format '{{.Config.User}}'
如果 .Config.User 为空,通常表示按镜像默认用户运行;如果是 1000:1000、www-data 这类值,就要确认宿主机挂载目录是否允许这个用户写入。
本地目录权限可以这样看:
bash
ls -ld ./data
如果容器需要写入 ./data,但宿主机目录对容器用户不可写,服务会在启动阶段直接失败。这里别粗暴 chmod 777,更推荐按实际运行用户修正目录所有者或权限。
5 排查端口:容器 Up 不等于服务能访问
很多 Docker 问题卡在这一步:容器状态是 Up,日志也没报错,但是浏览器打不开。
先看容器端口映射:
bash
docker port demo-web
如果输出类似下面这样,说明容器内 8080/tcp 已经映射到宿主机 8080:
bash
8080/tcp -> 0.0.0.0:8080
如果没有输出,说明这个容器没有发布端口。需要重新创建容器时加上 -p:
bash
docker run -d --name demo-web -p 8080:8080 nginx:latest
这条命令只是端口映射示例:宿主机 8080 映射到容器内 8080。如果你的应用实际监听 3000,就要写成 -p 8080:3000,别把宿主机端口和容器内端口混在一起。

这张图建议展示 docker port demo-web 和浏览器访问 127.0.0.1:8080 的结果。读者能直观看到端口映射和页面访问之间的关系。
再确认宿主机端口有没有被占用。macOS 和 Linux 都可以用:
bash
lsof -iTCP:8080 -sTCP:LISTEN
如果端口已经被别的进程占用,Docker 创建容器时会报端口绑定失败。换一个宿主机端口即可:
bash
docker run -d --name demo-web-2 -p 18080:8080 nginx:latest
这时访问的是宿主机 18080:
bash
curl -I http://127.0.0.1:18080
如果你用的是 Web 框架,还要确认应用监听地址。容器内服务只监听 127.0.0.1 时,外部端口映射也会访问不到。服务应监听 0.0.0.0,让容器网络接口能够接收请求。
6 排查网络:容器之间访问要用服务名,不要写 localhost
容器里的 localhost 指的是容器自己,不是宿主机,也不是另一个容器。
这是很多初学者最容易卡住的地方。比如 Web 容器连接数据库时写了:
bash
DATABASE_URL=postgres://user:pass@localhost:5432/app
如果数据库不在同一个容器里,这个地址就错了。在 Docker Compose 的默认网络里,容器之间应使用服务名访问,例如:
bash
DATABASE_URL=postgres://user:pass@db:5432/app
看容器网络可以用:
bash
docker inspect demo-web --format '{{json .NetworkSettings.Networks}}'
如果你想确认两个容器是否在同一个网络里,分别 inspect 它们的 NetworkSettings.Networks,网络名一致才好继续查应用层连接。
Compose 项目里也可以直接看网络:
bash
docker network ls
docker compose ps
如果容器压根没进同一个网络,服务名解析就不会生效。这个时候别继续改数据库密码,先把网络关系理顺。
7 恢复服务后,用 cpolar 做公网访问验证
本机能访问,不代表外部也能访问。尤其是你要给同事验收、调试 Webhook、让外部系统回调本地接口时,只测 127.0.0.1 不够。
服务恢复后,可以用 cpolar 给本地容器服务开一个临时公网访问地址。这里的目的不是把排障文章写成工具广告,而是补上"外部访问链路"的末端一环。
以本地容器服务监听宿主机 8080 为例:
bash
cpolar http 8080
命令启动后,终端会输出公网访问地址。把这个地址发给同事,或者填到 Webhook 配置里,就能验证外部请求是否能打到本机服务。
如果还没安装 cpolar,Linux 可使用官方一键安装脚本:
bash
curl -L https://www.cpolar.com/static/downloads/install-release-cpolar.sh | sudo bash
macOS 可通过 Homebrew 安装:
bash
brew tap probezy/core && brew install cpolar
安装后先确认本地管理端口:
bash
cpolar version
curl -s http://127.0.0.1:9200 || echo "服务未启动"
提醒一下:免费套餐生成的是随机临时公网地址,24 小时内会变化。临时演示、排障闭环够用;如果要长期给固定地址,固定二级子域名需要基础套餐或以上,自定义域名和固定 TCP 地址需要专业套餐或以上。

这张图可以放 cpolar 输出的公网地址,以及用公网地址打开容器页面的结果。看到公网地址能访问,才说明"容器服务、本机端口、外部访问"三段链路都通了。
如果公网地址打不开,按这条顺序查:
bash
curl -I http://127.0.0.1:8080
curl -s http://127.0.0.1:9200 || echo "cpolar 服务未启动"
本地 8080 不通,先回到 Docker 端口和应用日志;9200 不通,先检查 cpolar 服务;本地都通但公网不通,再看 cpolar 在线隧道列表里的地址是否复制完整。
8 总结:按顺序查,别在一个坑里硬改
这次排障链路做完后,你应该已经能判断 Docker 容器启动失败到底卡在哪一层:容器状态、启动日志、端口映射、目录权限、环境变量、容器网络,或者外部访问链路。
- 容器没起来:先看
docker ps -a、docker inspect、docker logs --tail 100 - 容器起来但打不开:重点查
docker port、宿主机端口占用、应用监听地址 - 本机能开但外部不通:用 cpolar 开 HTTP 隧道,验证公网访问链路
我更推荐按这个顺序排,而不是看到报错就重建容器。Docker 的现场信息很值钱,日志、inspect、端口映射能帮你少走很多弯路。后续如果服务要长期给团队使用,再考虑 Compose 固化配置、健康检查、固定域名和更严格的访问控制,会比临时修修补补省事很多。