更新于
部署后容器秒退:按 Docker 退出码判断原因
CI 显示部署成功,可没过几秒,docker ps -a 里就是 Exited (1)、Exited (137),或者容器一直在 Restarting 和退出之间来回切换,用户那边已经打不开了。本文写给刚把应用部署到 VPS 的开发者和创始人:先确认容器状态和退出码,再对照速查表看这个退出码通常意味着什么、不能说明什么,最后只读一小段日志,找出第一条异常。如果容器一直处在重启循环里,拿到退出码之后,接着看 Docker 容器反复重启怎么排查。
下面的命令都是只读的。只在你有权限的服务器上执行,把 YOUR_CONTAINER 换成实际的容器名或 ID。
1. 先确认状态和退出码
docker ps -a --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}'
docker inspect --format '{{.State.ExitCode}} {{.State.OOMKilled}} {{.State.Error}}' YOUR_CONTAINER
用 Compose 部署的话,docker compose ps -a 会列出这个项目的所有容器(包括已停止的),容器名一般是 <项目名>-<服务名>-1。
Exited (1) 8 seconds ago:主进程退出了一次,退出码是 1。Restarting (1) 3 seconds ago:重启策略在反复拉起它,括号里是上一次退出的退出码。inspect 这一行输出三个值:退出码、Docker 有没有记录到内存不足被杀(
true/false)、Docker 自己的报错信息。这三个值回答的是不同的问题,分开看。State.Error主要在 Docker 自己没能把进程启动起来时才有内容(命令写错、端口已被占用等)。大多数应用崩溃时它是空的,所以空不代表正常。状态是
Created:容器建好了,但从来没跑起来过。问题通常出在启动命令或镜像上,而不是你的业务代码。
想知道进程实际跑了多久、有没有重启策略在起作用:
docker inspect --format 'started={{.State.StartedAt}} finished={{.State.FinishedAt}} restarts={{.RestartCount}} policy={{.HostConfig.RestartPolicy.Name}}' YOUR_CONTAINER
started 和 finished 只差几秒,就是典型的“秒退”。这两个时间是 UTC(结尾带 Z),和部署时间、按本地时间记录的宿主机日志对比时,先换算成同一个时区(北京时间 = UTC + 8 小时)。
这一步不能说明:进程为什么退出。它也不能保证你看的是对的容器,顺手核对一下容器名和镜像 tag 是不是你刚部署的那一个。
2. 退出码速查:下一步跑什么,它不能说明什么
| 退出码 | 通常含义 | 下一步 | 不能说明 |
|---|---|---|---|
| 0 | 主进程正常结束 | 看容器到底被配置成运行什么(命令见下文)。Web 服务以 0 退出,多半是进程把自己放到了后台,或者启动命令本来就是一次性任务。如果是迁移、备份这类一次性任务,0 就是预期结果。 | 代码有问题,或者服务一切正常。它只说明主进程没有报错就结束了。 |
| 1 | 应用自己报错(通用错误码) | 看日志(第 3 步),找第一条异常;对照这次部署改了什么:镜像 tag、环境变量、配置、数据库迁移。 | 具体是哪个错误。很多语言和框架遇到任何未捕获的异常或启动检查失败,都是退出 1。 |
| 125 | Docker 自己出错:docker run 命令或 daemon 拒绝了这次请求 | 看 CI / 部署日志里 docker run 或 docker compose up 打印的报错,以及 State.Error。常见的有参数写错、镜像拉取被拒、端口已被占用(这时用 sudo ss -ltnp 看是谁占着端口)。 | 应用有 bug。通常你的进程根本没启动。 |
| 126 | 找到了命令,但执行不了 | 检查配置的启动命令,以及镜像里这个文件的权限和格式(命令见下文)。bind mount 挂进来的脚本沿用宿主机上的文件权限,宿主机上没有执行权限,这里就会报 126。 | 文件不存在。126 是“执行不了”,127 才是“找不到”。 |
| 127 | 找不到命令 | 同样先检查启动命令。确认这个程序在这个镜像里存在,并且在 PATH 上;换了更精简的基础镜像、Compose 的 command: 写错,都很常见。容器里的 shell 脚本调用了不存在的命令,也会以 127 退出。 | 程序启动后崩溃了。你想运行的程序根本没跑起来。 |
| 137 | 被 SIGKILL 杀掉(128 + 9) | 看第 1 步里的 OOMKilled。是 true:内存是首要怀疑方向,查内存限制和内核日志(命令见下文)。是 false:用 docker events 看是谁发的 kill,比如有人执行了 docker kill,或者应用不理会 SIGTERM,docker stop 等满宽限期(默认 10 秒)后强制杀掉。 | 内存不够。光有 137 不等于 OOM;即使 OOMKilled=true,也要和宿主机内存、内核日志的时间对上。详见 退出码 137 排查。 |
| 139 | 被 SIGSEGV 终止(128 + 11),段错误 | 看退出前后的日志,再查内核日志里有没有 segfault 记录(会写出出错的程序或库)。留意这次部署有没有升级原生依赖、基础镜像或运行时。 | 问题出在哪里。它只说明进程访问了非法内存,不说明是哪个组件、为什么。 |
| 143 | 收到 SIGTERM 后退出(128 + 15) | 查是谁让它停的:重新部署、docker stop、docker compose down / up 重建容器、宿主机关机,或者编排工具。docker events 加上你的部署时间线,一般就能回答。 | 程序崩溃了。143 是正常的停止请求,真正要问的是谁发的,以及替换它的新容器有没有起来。 |
125、126、127 可能来自两个地方。 如果是 Docker 没能把进程启动起来,它们是 docker run 命令本身的退出码:你会在 CI 或部署日志里看到,容器通常停在 Created,报错信息在 State.Error 里(如果 docker run 返回的是 125,docker inspect 里容器自己的 ExitCode 可能是别的值,比如 128)。如果是容器已经跑起来、里面的 shell 或入口脚本遇到同样的问题,容器本身就会以 126 或 127 退出,报错在 docker logs 里。
大于 128 的退出码一般表示“被信号(退出码 − 128)终止”。在 bash 里执行 kill -l 137 会输出 KILL,遇到不认识的退出码可以这样换算。其他数字是应用自己定义的返回值,含义以它的日志或文档为准。
表里提到的命令
查看容器运行的是什么(对应 0、126、127):
docker inspect --format 'entrypoint={{json .Config.Entrypoint}} cmd={{json .Config.Cmd}}' YOUR_CONTAINER
docker image inspect --format '{{.Os}}/{{.Architecture}}' YOUR_IMAGE
uname -m
后两条对比镜像平台和宿主机架构(比如镜像是 linux/arm64,宿主机是 x86_64)。架构不匹配时,日志里通常会出现 exec format error。
内存和信号(对应 137、139、143)。docker events 只能返回 daemon 还保留着的最近事件(最后 256 条),所以出事后尽早查:
docker inspect --format 'oom={{.State.OOMKilled}} memory_limit_bytes={{.HostConfig.Memory}}' YOUR_CONTAINER
docker events --since 1h --until "$(date +%s)" --filter container=YOUR_CONTAINER
free -h
sudo dmesg -T | grep -i -E 'out of memory|killed process|segfault' | tail -n 20
memory_limit_bytes=0表示没给容器设内存上限,上限就是宿主机的内存。在
docker events里,先看到一条kill(signal=15),约 10 秒后又一条signal=9,接着die(exitCode=137):这是“忽略了 SIGTERM,停止时被强制杀掉”,不是 OOM。内核日志的时间要和第 1 步里的
finished对得上才算证据。dmesg -T显示的是宿主机本地时间,机器挂起过的话,换算出来的时间可能有偏差。
这组命令不能说明:free 和 docker stats 只反映现在,看不到被杀那一刻的峰值;已经停止的容器通常根本没有 stats 数据。另外,视具体环境而定,宿主机层面的 OOM 不一定会让 Docker 标记 OOMKilled。
3. 只读一小段日志,找第一条异常
docker logs --since 30m --timestamps YOUR_CONTAINER 2>&1 | head -n 80
docker logs --since 30m --timestamps YOUR_CONTAINER 2>&1 | grep -n -i -E 'error|fatal|panic|exception|denied|not found|refused' | head -n 20
--since也可以写成时间点,比如2026-09-29T14:05:00(不带Z时按本地时间理解),从部署前一点开始看。用 Compose 的话,docker compose logs --since 30m --timestamps SERVICE可以按服务看。2>&1不能省:docker logs会把容器的 stderr 原样输出到 stderr,而大部分报错都在那里,不加的话head和grep会漏掉。看部署之后的第一条异常,而不是只看最后一行。重启循环里同样的错误会反复出现,第一次出错前的那几行往往才是关键线索。
没有输出不代表应用没问题:它可能把日志写到了容器里的文件,或者当前的日志驱动不支持
docker logs读取。
常见的第一条异常(在核实之前都只是假设):
connection refused/could not connect:依赖服务没起来,或者地址写错了。容器里的localhost指的是容器自己,不是另一个服务。permission denied:挂载目录的文件属主,或者镜像里的运行用户。脚本明明存在,却报
no such file or directory:常见原因是脚本第一行带了 Windows 换行符(CRLF),或者镜像里没有它要的解释器。exec format error:镜像是为另一种 CPU 架构构建的(见上面的平台检查)。缺少环境变量或配置项:和上一次正常的部署对比键名,不要把密钥的值打印出来。
把日志贴到群里、工单或 AI 之前,先遮掉密码、令牌、连接串、客户邮箱和内部地址。
在 OpsMate 里怎么做
OpsMate 把 SSH 终端和 AI 放在同一个服务器页面。上面的 docker ps、docker inspect、docker logs 你都可以直接手敲,查退出码这种事通常手敲最快。也可以用一句话把问题告诉 AI,比如“查一下 YOUR_CONTAINER 为什么部署后就退出,总结日志里最早出现的几条错误”。AI 会提出排查命令,除了看日志,也可以用 docker、journalctl、ps、df、ss 这类命令做检查;命令跑完后,点击「需要分析」,AI 会给出结论。命令和输出都留在终端里,你可以拿原始日志核对 AI 的结论。点击「需要分析」后,命令输出会发送给云端 AI 分析;桌面端默认保证的只是 SSH 凭证留在本机。日志里有客户数据或密钥时,建议自己手敲命令,只把脱敏后的片段交给 AI。
边界
退出码只是线索,不是诊断结论。 同一个数字可能有好几种原因,1 和 137 尤其如此。结论要同时有退出码、日志内容和对得上的时间。
本文的命令不会改动你的容器。 AI 以排查为主:危险命令会被拦截。重启服务、日志轮转这类低风险处置默认会自动执行。回滚镜像、改 Compose 文件、改重启策略、调大内存上限,都是由你决定的变更,事先准备好回退办法。OpsMate 自己会做什么、不会做什么,以官网 常见问题 为准。
AI 的总结是排查起点,要用 inspect 输出和日志时间核对。
SSH 连不上宿主机时,OpsMate 也连不上,先去云厂商控制台处理。
说明性示例
说明性示例(不是真实客户案例):把镜像换成更精简的基础镜像后,docker ps -a 显示 Restarting (127) 4 seconds ago。docker inspect 输出 127 false,报错信息为空,说明 Docker 已经把进程启动起来了,也不是内存被杀。started 和 finished 只差一秒左右。docker logs --since 15m --timestamps 的第一行是 /app/start.sh: 5: exec: gunicorn: not found:入口脚本跑起来了,但新的基础镜像里没有应用服务器。这时改重启策略、加内存都没用。下一步由人来决定:回滚到上一个镜像 tag,或者重新构建时把依赖装上,然后观察容器能不能稳定运行。
AI 排查提示词
这台服务器上有个容器部署后几秒就退出。请先只做检查、不做任何改动,看看:它的 State.ExitCode、OOMKilled、Error、StartedAt、FinishedAt 和重启次数,汇总部署之后日志里最早出现的错误(带时间戳),并说明这个退出码通常意味着什么、不能说明什么。请区分已证实的事实和推测,列出还缺少的信息。不要重启或删除容器,不要修改重启策略、Compose 文件或内存上限。如果需要变更,先给出最小步骤、风险、验证方法和回退方案,等我确认。
根据证据选择下一步
容器一直
Restarting?看 Docker 容器反复重启怎么排查,里面讲了重启策略、日志时间窗口,以及如何区分内存不足和其他终止原因。退出码 137 或
OOMKilled=true?看 退出码 137 是不是内存不够。日志提示磁盘写满?看 Docker 日志占满磁盘。
想让终端和 AI 并排工作?看 终端里的「一句话 AI + 手敲命令」。
试用
每月免费 500 次 AI 调用,服务器数量不限。桌面端默认把 SSH 凭证保留在本机。
需要帮助理解排查结果?
OpsMate 帮助开发者和运维人员用 AI 辅助排查,由你核对证据。点击「需要分析」后,命令输出会发送给云端 AI 分析,请先去除敏感信息。
免费开始 本地凭证桌面端