部署网站、数据库或自托管工具时,经常会遇到这样的情况:镜像已经拉取完成,容器也显示启动成功,但过几秒服务就无法访问。执行查看命令后,发现容器变成了 Exited,或者一直处于 Restarting 状态。
这类问题需要结合容器状态、应用日志和退出事件判断。配置缺失、主进程提前结束、依赖服务未就绪,以及内存不足,都可能表现为“容器启动就退出”。
本文适用于 Linux 主机上的 Docker Engine,涉及项目配置的部分使用 Docker Compose V2,即 docker compose 命令。

一、先查看容器状态和退出信息
在运行 Docker 的服务器上执行:
docker ps -a --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}'
docker ps 默认只显示正在运行的容器,增加 -a 才能看到已经退出的容器。
常见状态可以这样理解:
| 状态 | 含义与检查方向 |
|---|---|
Exited (0) | 主进程以成功状态结束,需要确认它是否本来就应该持续运行 |
Exited (1) | 常见于应用启动失败,具体原因需要查看日志 |
Exited (137) | 常见于进程被强制终止,需要结合 OOM、停止操作及系统记录判断 |
Restarting | 容器退出后正在按重启策略重新启动 |
Created | 容器已创建,但尚未成功进入运行状态 |
退出码是排查线索,不能脱离应用日志单独下结论。
先将下面的 myapp 改成实际容器名,后续命令在同一个终端中执行:
container_name='myapp'
读取详细状态:
docker inspect --format \
'Status={{.State.Status}}
ExitCode={{.State.ExitCode}}
OOMKilled={{.State.OOMKilled}}
RestartCount={{.RestartCount}}
Error={{json .State.Error}}' \
"$container_name"
重点关注:
ExitCode:进程退出时返回的状态。OOMKilled:Docker 是否记录到与该容器相关的 OOM 终止。RestartCount:容器重启次数。Error:容器启动或运行过程中由 Docker 记录的错误。
这些字段反映当前容器状态,不是完整的历史故障记录。 容器已经自动重启时,还需要结合后面的日志和事件查询。
二、通过日志定位应用启动错误
查看最近 100 行日志,并显示时间:
docker logs --tail 100 --timestamps "$container_name"
重点找退出前出现的错误,以及它前面几行的上下文。
| 日志内容 | 优先检查 |
|---|---|
permission denied | 运行用户、挂载目录权限、只读挂载及安全策略 |
no such file or directory | 文件路径、挂载位置、启动脚本及解释器是否存在 |
| 缺少环境变量或配置项 | Compose 的 environment、env_file 和应用配置文件 |
| 数据库连接被拒绝 | 数据库地址、端口、网络及服务就绪状态 |
| 配置解析失败 | YAML、JSON 或应用配置文件的语法和参数 |
例如,挂载目录存在,并不意味着容器中的应用用户一定有权限读写。
可以查看容器用户和实际挂载关系:
docker inspect --format \
'User={{json .Config.User}}
{{range .Mounts}}{{.Type}} {{.Source}} -> {{.Destination}} RW={{.RW}}
{{end}}' \
"$container_name"
其中,Source 是挂载来源,Destination 是容器内部路径,RW=false 表示只读挂载。应根据应用要求修正具体目录的权限,避免直接对整个目录树执行 chmod 777。
如果 docker logs 没有输出,也不能判断应用没有报错。应用可能把日志写入文件,或者使用了无法通过该命令读取的日志配置。此时需要继续查看应用自己的日志位置,以及前一步的 Error 字段。
三、Exited (0):检查主进程是否提前结束
容器的主进程结束后,容器也会退出。因此,退出码为 0,可能只是启动命令已经执行完毕。
常见情况包括:
- 容器只运行了一次性初始化脚本。
- 启动脚本把应用放到后台,随后脚本自身退出。
- 自定义
command覆盖了镜像原有启动命令。 - 执行的是打印信息、迁移数据等一次性任务。
查看实际启动程序和参数:
docker inspect --format \
'Path={{json .Path}}
Args={{json .Args}}
Entrypoint={{json .Config.Entrypoint}}
Cmd={{json .Config.Cmd}}' \
"$container_name"
如果目标是持续运行的服务,应使用应用支持的前台运行方式。
例如,自建 Nginx 镜像可以在 Dockerfile 中使用以下默认命令:
CMD ["nginx", "-g", "daemon off;"]
这是 Dockerfile 配置示例。修改后需要重新构建镜像,并更新对应容器。
使用现成镜像时,应先核对镜像文档,检查是否错误覆盖了默认的 command 或 entrypoint。对于本来就只执行一次的任务,完成后退出属于预期行为。
四、Exited (137):结合 OOM 和系统事件判断
退出码 137 不能直接等同于内存不足。
内存不足触发 OOM、手动强制结束进程,以及停止超时后被强制终止,都可能产生类似结果。
先查看 Docker 配置中的内存上限:
docker inspect --format \
'MemoryLimitBytes={{.HostConfig.Memory}}' \
"$container_name"
输出单位为字节。值为 0 表示没有通过这个配置项设置内存硬上限,并不代表可用内存无限。
查看宿主机当前内存:
free -h
在使用 systemd 的 Linux 主机上,可以查询近期内核日志:
sudo journalctl -k --since "30 minutes ago" --no-pager \
| grep -Ei 'out of memory|oom-kill|killed process'
将记录中的时间、进程及容器相关信息,与故障发生时间进行对应。当前内存已经恢复空闲,也不能排除此前发生过 OOM。
对于反复重启的容器,还可以查询最近 30 分钟的事件:
docker events \
--since 30m \
--until 0s \
--filter "container=$container_name"
关注 oom、kill、die、start 等事件及其先后顺序。Docker 历史事件有保留数量限制,查不到旧事件不代表故障没有发生。
确认内存问题后,再根据实际情况处理:
- 容器限制过低、宿主机仍有余量:合理提高容器内存上限。
- 宿主机整体内存不足:减少并发或其他服务占用,必要时扩容。
- 应用内存持续增长:检查缓存、任务规模及内存泄漏。
仅增加重启次数,并不能消除持续存在的内存问题。
五、修正 Compose 配置,并让修改真正生效
如果使用 Docker Compose,应进入原项目目录,并沿用原来的配置文件、项目名和环境文件参数。
先检查配置:
docker compose config -q
正常情况下,该命令校验通过时不会输出内容;出现错误时,应先修正。校验通过只说明 Compose 配置可以被解析,不代表应用参数、数据库账号等内容一定正确。
查看项目中的服务名称:
docker compose config --services
这里的服务名可能与容器名不同。例如,服务名是 app,实际容器名可能是 project-app-1。
如果修改了环境变量、启动命令、挂载或其他 Compose 配置,仅执行 docker compose restart 不会应用这些配置变化。
只修改了 app 服务,且数据库等依赖已经正常运行时,可以执行:
docker compose config -q && docker compose up -d --no-deps app
将 app 替换为实际服务名。配置变化可能触发目标容器重建和短暂中断,执行前应确认业务数据已经持久化并做好备份。
--no-deps 不会启动依赖服务。如果数据库本身没有运行,需要先处理数据库,不能直接套用这一选项。
另外,数据库容器显示“运行中”,不一定意味着它已经可以接受连接。若日志表明应用启动早于数据库就绪,应配置合适的健康检查,并结合 depends_on 的 service_healthy 条件及应用自身的连接重试机制处理。
六、验证容器和业务是否恢复
完成修改后,查看项目状态:
docker compose ps -a
查看目标服务日志:
docker compose logs --tail 100 --timestamps app
除了确认容器处于运行状态,还需要检查:
- 观察时间覆盖此前的故障触发周期,容器不再反复退出。
- 日志中不再出现同一启动错误。
- 网站、接口或客户端可以完成一次实际请求。
- 涉及数据库和文件存储的服务,读写功能及原有数据正常。
容器能够启动,是恢复的第一步。将退出原因与日志对应起来,修正具体配置,再验证真实业务操作,才能确认这次故障已经解决。






