Docker 容器退出码非零速查:137、139、1 与 logs、inspect 读码法
更新时间:2026-10-24。FAQ 速查页,面向
docker ps -a里躺着一排Exited (137)、Exited (1)却不知道为什么的同学。容器退出码非零的本质是「谁让它死的」:进程自己走到 exit 并报告了错误码(1-125 由应用定义)、被信号打死(128+N,N 是信号编号)、或者被 Docker 拦在执行之前(126/127)。退出码是死者的「死亡证明」,但真正的原因往往写在docker logs的最后几行和docker inspect的 State 字段里。病因收敛在六根轴上:137 的 OOM 被杀与显式 kill、139 的段错误、1 的应用层报错、126/127 的入口命令问题、0 秒退出的正常使命、以及 restart 策略掩盖的循环崩溃。顺一遍基本必中。如果是容器根本没起来而不是退出码问题,转 Docker 语法解析报错速查。
本文要回答的问题
Exited (137)是 OOM 还是被谁 kill 了,怎么区分?Exited (139)段错误,容器镜像没坏为什么一跑就崩?- 退出码 1 去哪找应用自己的报错信息?
Exited (126)和Exited (127)差在哪,为什么改 entrypoint 后常踩?- 容器反复重启,怎么看到「上一次」的退出原因?
一、报错原文与典型现象
退出码现场集中在三处:docker ps -a 的状态列、docker inspect 的 State 段、以及应用日志的尾巴(镜像名以 registry.example.com 占位,路径以 /proj/demo 占位):
# ps -a 状态列
CONTAINER ID STATUS NAMES
a1b2c3d4 Exited (137) 2 hours ago web
e5f6a7b8 Exited (1) 5 minutes ago api# inspect 的 State 段(关键字段)
docker inspect web --format '{{.State.ExitCode}} {{.State.OOMKilled}} {{.State.Error}}'
137 true ""# 入口命令类
docker: Error response from daemon: failed to create task:
exec: "/proj/demo/start.sh": stat /proj/demo/start.sh:
no such file or directory: unknown.
# 或容器日志尾行
/bin/sh: ./server: not found读现象先分三档:137 = 128+9 是被 SIGKILL 打死(OOM 或显式 kill);139 = 128+11 是被 SIGSEGV 打死(段错误);1 是应用自己体面地退出并报告失败。126 是「命令存在但不可执行」,127 是「命令找不到」——这两个不是进程死亡,是进程根本没生出来。
二、病因处置速查表
| 报错现象 / 场景 | 可能原因 | 快速验证 | 处置 |
|---|---|---|---|
| Exited (137) 且 OOMKilled=true | 容器内存超限被 cgroup 杀 | inspect 看 OOMKilled 字段 | 调大 memory limit 或治内存泄漏 |
| Exited (137) 且 OOMKilled=false | 被 docker kill / stop 超时强杀 | 查操作记录与 stop 超时 | 应用支持优雅退出,调 stop-grace-period |
| Exited (139) | 进程段错误(SIGSEGV) | logs 尾部找 segfault 字样 | 对照核心转储定位,查架构/依赖 |
| Exited (1) | 应用自身报错退出 | logs 最后几十行 | 按应用报错修配置/依赖/数据 |
| Exited (127) | entrypoint/cmd 不存在 | inspect 看 Config.Entrypoint | 修镜像内路径或 command 拼写 |
| Exited (126) | 入口文件无执行权限或 shebang 错 | 进镜像 ls -l 看权限位 | 构建期 chmod +x,shebang 修正 |
| 秒退且退出码 0 | 前台进程执行完即退出 | logs 看是否正常跑完 | 服务型容器要有前台常驻主进程 |
三、排查决策图
下面这张图是标准排查路线,颜色按角色区分:红是报错入口与命中的根因,蓝是排查动作,绿是通过分支,橙是修复动作,紫是防复发沉淀。
读这张图抓分水岭:ExitCode 是否大于等于 128 是第一刀——超过 128 说明死于信号,先去查 OOMKilled;小于 128 说明是应用层退出,直接去 logs 尾部找报错。两路用错药很常见:拿着 137 去改应用配置、拿着退出码 1 去调内存,都是浪费轮次。
四、逐条排查展开
137 的第一要务是区分 OOM 与人为 kill。 同样是 128+9(SIGKILL),来源完全不同:docker inspect 的 State.OOMKilled 为 true 是内核 cgroup 因为内存超限动的手,false 则是有谁执行了 docker kill,或者 docker stop 等优雅期超时后补的强杀。OOM 的处置是调大 --memory 上限或治应用的内存泄漏(宿主机 dmesg 里也能看到 oom-kill 记录);人为 kill 的处置是看为什么有人/有编排系统要杀它——健康检查失败、滚动更新、资源调度都可能。编排环境(compose watchtower、k8s 探针)里的 137 要顺带怀疑探针配置过严。
139 段错误:镜像没坏,是运行环境变了。 SIGSEGV 最常见于两类:一是镜像架构与宿主机不匹配(arm 镜像跑 x86 机器且没装模拟层时通常直接 exec 失败,但某些模拟层下半残运行会段错误),二是应用依赖的指令集或内核特性在当前环境缺失。排查顺序:logs 尾部找 segfault 字样 → docker inspect 看镜像 Architecture → 换宿主机或换对应架构镜像对照。详细的段错误定位方法见 Docker 段错误崩溃速查。
退出码 1 的答案永远在 logs 里。 1 是 POSIX 约定的「通用错误」,应用自己 exit(1) 时一定在 stdout/stderr 里留了遗言。标准动作是 docker logs --tail 100 容器名 直接看最后几十行——配置缺失、端口被占、依赖服务连不上、数据文件损坏都写在那。坑在两点:应用把日志写进了容器内文件而不是 stdout(logs 是空的,要进容器找日志文件);restart 策略开着导致容器已重启多轮,docker logs 只给当前轮,要看上一轮用 docker logs --previous(仅编排系统重建前的容器实例有效,普通场景靠 logs 加时间戳参数)。
126/127 是「没生出来」不是「死了」。 127 是 shell 找不到入口命令:entrypoint 写的路径在镜像里不存在、构建时 COPY 漏了、或 command 拼错。126 是文件在但执行不了:没有执行权限位,或脚本 shebang 指向的解释器镜像里没有(如 alpine 里跑 #!/bin/bash 的脚本)。验证最快的方式是起个临时容器覆盖入口:docker run --rm -it --entrypoint sh 镜像名,进去 ls -l 看路径与权限位。修法都在构建期:Dockerfile 里 RUN chmod +x、确认 COPY 目标路径、alpine 镜像脚本改用 #!/bin/sh。
秒退且退出码 0 也是一种「非零排查」的常客。 很多人拿着「容器一启动就退出」来查,结果退出码是 0——这不是故障,是主进程跑完了。容器的生命周期等于 PID 1 的生命周期:命令是 echo hello,打印完容器使命结束。服务型容器必须有一个前台常驻主进程,不能是后台 daemonize 的程序(daemonize 后父进程退出,PID 1 结束,容器跟着退)。
restart 策略会掩盖真相。 --restart always 配上一个必崩的入口,表现是容器「一直在跑」但服务不可用,ps 里 STATUS 是 Up 几分钟、实际上已经循环死了几十次。用 docker inspect --format '{{.RestartCount}}' 看重启计数,计数飙升就停掉 restart 策略,以前台模式 -it 跑一次完整观察从启动到死亡的全过程。
compose 场景多读一处。 docker compose ps 同样显示各服务的退出码,但真正的排查入口是 docker compose logs --tail 100 服务名——多服务栈里一个服务退出码 1,根因常在它的依赖服务先死了(比如 web 连不上先崩的 db)。养成习惯:栈里任何一个服务非正常退出,先按启动顺序把上游服务的日志从尾往前翻一遍,再回头处理报错的那个。另外 compose 的 restart: unless-stopped 与裸 run 的 restart 语义一致,掩盖问题的方式也一致,排查时先 docker compose stop 再单服务 docker compose run --rm 前台复现。
五、规范做法与预防
- 资源限制显式给:内存上限按应用基线给并配 OOM 告警,别让 137 第一次出现时才知道没限。
- 优雅退出标准化:应用接住 SIGTERM 做清理,stop-grace-period 按清理耗时给够,减少被强杀的 137。
- 入口命令进镜像自测:CI 里加一步
docker run --rm 镜像名 --version类冒烟,126/127 在构建期拦截。 - 日志一律走 stdout/stderr:容器内应用不写本地日志文件,保证
docker logs有遗言可读。 - restart 策略配监控:RestartCount 纳入巡检指标,循环崩溃要报警而不是默默重启。
- 架构镜像归位:多架构镜像打 manifest list,避免 arm/x86 错配跑出 139。
这一篇在知识体系里的位置
本篇是 Docker 排错速查簇的生命周期篇,定位「进程为什么死」:139 段错误的深入定位看 Docker 段错误崩溃速查;OOM 与资源超限的系统性治理看 Docker 资源耗尽速查;容器收不到 SIGTERM 导致 stop 超时被强杀看 Docker 信号收不到速查。「ExitCode 是否大于等于 128 分信号致死与应用退出,logs 尾部与 OOMKilled 字段定真凶」这条主线对一切退出码报错通用。
一句话总结
容器退出码先算一刀:大于等于 128 是死于信号——137 查 OOMKilled 字段区分 OOM 与人为 kill,139 是段错误去查架构与依赖;小于 128 是应用层——退出码 1 的答案在 docker logs 尾部,127 是入口命令不存在、126 是无执行权限或 shebang 错,都在构建期修。秒退但退出码 0 是主进程本就跑完了,服务型容器要前台常驻主进程。restart 策略会掩盖循环崩溃,用 RestartCount 与前台重跑看全生命周期。限制显式给、日志走 stdout、入口命令 CI 冒烟,退出码类报错基本绝迹。