更新于

部署后容器秒退:按 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。

想知道进程实际跑了多久、有没有重启策略在起作用:

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。
125Docker 自己出错: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

这组命令不能说明: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

常见的第一条异常(在核实之前都只是假设):

把日志贴到群里、工单或 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。

边界

说明性示例

说明性示例(不是真实客户案例):把镜像换成更精简的基础镜像后,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 文件或内存上限。如果需要变更,先给出最小步骤、风险、验证方法和回退方案,等我确认。

根据证据选择下一步

试用

每月免费 500 次 AI 调用,服务器数量不限。桌面端默认把 SSH 凭证保留在本机。

需要帮助理解排查结果?

OpsMate 帮助开发者和运维人员用 AI 辅助排查,由你核对证据。点击「需要分析」后,命令输出会发送给云端 AI 分析,请先去除敏感信息。

免费开始 本地凭证桌面端

排障指南