docker ps 里容器状态一直显示 Restarting (1) xx seconds ago,或者刚 up 没多久又变成 Exited——这是用 Docker 时最常碰到的故障之一。容器反复重启,本质就一句话:容器主进程退出了,而重启策略又把它拉起来,于是循环。排查的思路也很直接,就是去找”主进程到底为什么退出”。下面按步骤给出能直接复制的命令。
容器重启原因对比
| 状态码 | 含义 | 排查方向 |
|---|---|---|
| Exit 0 | 正常退出但 restart:always | 检查是否应该用 restart:unless-stopped |
| Exit 137 | OOM 被杀 | docker inspect 看 OOMKilled |
| Exit 1 | 应用报错退出 | docker logs 看错误 |
| Exit 255 | 入口脚本错误 | 检查 entrypoint / cmd |
| Restarting | 反复重启 | 看 logs 是否启动即崩 |
排查流程
→
→
→
第一步:看容器日志,绝大部分问题在这一步就能看出来
docker logs --tail 100 容器名
docker logs -f 容器名 # 实时跟踪重启瞬间的输出
常见的日志线索有这些:配置文件路径写错、数据库连不上、端口被占用、缺环境变量、应用自己抛异常。日志能看懂的,直接回去修应用问题就行;日志为空或者看不出名堂的,继续下一步。
第二步:查退出码,判断退出类型
docker inspect 容器名 --format '{{.State.ExitCode}} {{.State.OOMKilled}} {{.State.Error}}'
退出码对照如下:
- 0:进程正常结束——通常是容器里没有前台常驻进程(见第四步);
- 1:应用报错退出——回去细看 docker logs;
- 137:被 SIGKILL 杀掉,若 OOMKilled 为 true 就是内存超限;
- 126/127:启动命令不可执行/找不到——检查 entrypoint 和 command 的拼写;
- 139:段错误,多半是程序或依赖库自己的缺陷。
第三步:内存不足(退出码 137)的处理
先确认宿主机整体内存和容器的限制:
free -h
docker stats --no-stream
如果容器设了 -m 内存上限,而且经常打满,要么调大限制,要么优化应用(比如给 Java 应用设 -Xmx 小于容器限制,给 MySQL 容器调低 innodb_buffer_pool_size)。宿主机整体内存不够时,可以临时加 swap 应急:
fallocate -l 2G /swapfile && chmod 600 /swapfile
mkswap /swapfile && swapon /swapfile
第四步:退出码 0 却一直重启?主进程没有常驻
容器要求 PID 1 是一个前台常驻的进程。下面这些写法都会”启动即退出”:入口命令是一条执行完就结束的脚本、nginx 没加 daemon off、用了 service xxx start 这类后台化命令。修复示例:
# 错误:CMD service nginx start
# 正确:
CMD ["nginx", "-g", "daemon off;"]
调试时可以先用 sleep 把容器保住,再进去手动跑命令看报错:
docker run -it --entrypoint /bin/sh 镜像名
# 容器内手动执行原启动命令,直接观察报错
第五步:检查重启策略与依赖顺序
docker inspect 容器名 --format '{{.HostConfig.RestartPolicy.Name}}'
如果应用依赖数据库,而数据库容器还没就绪应用就启动失败,就会形成”启动-失败-重启”的循环。docker-compose 里应该用 healthcheck + depends_on 的 condition: service_healthy,而不是裸的 depends_on。另外磁盘写满(用 df -h 检查)也会让容器反复起不来。
常见问题
如何暂时阻止容器无限重启,方便排查?
先执行 docker update --restart=no 容器名 再 docker stop,排查修好后再改回 always 或 unless-stopped。
docker logs 什么都没有输出怎么办?
说明主进程在打印任何内容之前就退出了,多半是入口命令本身有误。用上面那个 –entrypoint /bin/sh 的方式进容器,手动执行启动命令来定位。
容器在宿主机重启后全部变成 Exited 正常吗?
如果重启策略是 no 就属于正常现象。希望开机自启的容器,应该设成 –restart unless-stopped。
如果你的容器问题按上面的步骤还没解决,欢迎到 fenij.com 评论区贴出 docker logs 和退出码,我们帮你一起看。
实操补充:如何正确地手动重启容器
排查完原因要重启时,命令用错也会踩坑。最常用的是 docker restart 容器名,它先发 SIGTERM 优雅停止、默认等 10 秒再 SIGKILL。如果你的服务关得慢(比如正在落盘),加个超时:docker restart -t 30 容器名 给足 30 秒。
在 docker-compose 项目里别直接 restart 容器,用 docker compose restart 服务名,它会按依赖顺序重启。下面这张表把几种「重启」的区别一次说清:
| 命令 | 作用 | 会重读配置吗 |
|---|---|---|
| docker restart | 停掉再拉起容器 | 否 |
| docker stop + start | 等价于 restart | 否 |
| docker compose restart | 按依赖重启服务 | 否 |
| docker compose up -d | 重建并启动(改了 yml 用它) | 是 |
restart 不会重新读配置。你改了 docker-compose.yml 想让改动生效,必须跑 docker compose up -d,光 restart 没用——这坑过不少人。
数据会不会丢?容器本身是无状态的,真正的数据在 volume 里。跑 docker inspect 容器名 --format '{{json .Mounts}}' 看挂载:凡是挂了 volume 或 bind mount 的目录,重启甚至删容器都不丢;只有写在容器可写层(没挂出来的部分)的临时文件会随容器消失。
开机自启:创建容器时加 --restart unless-stopped,或对已有容器执行 docker update --restart unless-stopped 容器名,机器重启后会自动拉起。
延伸答疑
docker restart 和 stop 再 start 有区别吗?
没本质区别,restart 就是一步到位的 stop+start,还自带 10 秒优雅停止超时,更省事。
重启后端口连不上 / IP 变了?
桥接网络下容器每次重启可能拿到新 IP,别在别的服务的配置里写死容器 IP,改用容器名或服务名做内网 DNS 解析;端口映射(-p)是固定的,外部照常用宿主机端口访问。
改了配置 restart 不生效?
restart 只读内存里当前进程状态,不读磁盘上的 compose 文件。改完 yml 跑 docker compose up -d 让它重新创建容器。
怎么让容器开机自动重启?docker update --restart unless-stopped 容器名;compose 项目在 yml 的 service 下加 restart: unless-stopped 再 up -d。