Docker 容器一直 Restarting,我见过最多的现场是这样:
docker ps
输出里某个服务反复变成:
Restarting (1) 12 seconds ago
Restarting (137) 8 seconds ago
Restarting ( unhealthy )
这时候很多人第一反应是重启 Docker:
systemctl restart docker
有时确实能临时缓一下,但大多数时候没用。因为容器一直重启,通常不是 Docker 引擎坏了,而是容器里的主进程启动后立刻退出。
也就是说,Docker 只是在按你的 restart 策略不停拉它起来。真正的问题往往藏在:
- 应用启动报错
- 环境变量缺失
- 配置文件路径不对
- 端口被占用
- 数据库没连上
- 文件权限不对
- 内存不够被 OOM kill
- healthcheck 写错
这篇按我自己排障时常用的顺序来,不讲空话,直接看命令和判断方法。
第一条命令不是 restart,而是:
docker ps -a
注意一定要加 -a。否则已经退出的容器你可能看不到。
你重点看这几列:
STATUS
NAMES
COMMAND
常见状态有:
| 状态 | 大概意思 |
|---|---|
Exited (1) | 应用自己报错退出,最常见 |
Exited (0) | 主进程正常结束,但不适合当常驻服务 |
Exited (137) | 很可能被 OOM kill 或收到 SIGKILL |
Restarting (1) | 启动失败后被 restart 策略反复拉起 |
Restarting (137) | 每次启动后被杀,优先查内存 |
如果你看到 Restarting (1),先别猜,直接看日志。
最常用的是:
docker logs container_name
但容器一直重启时,日志可能刷得很快。我更建议先这样看:
docker logs --tail=200 container_name
如果要持续观察:
docker logs -f --tail=100 container_name
如果你用 Docker Compose:
docker compose logs --tail=200 service_name
或者看全部服务:
docker compose logs --tail=200
很多问题日志里会直接写出来:
Error: Cannot find module
permission denied
connect ECONNREFUSED 172.18.0.3:5432
address already in use
missing required environment variable DATABASE_URL
no such file or directory
如果你看不懂日志,至少先把第一条报错找出来。不要只看最后一行,因为最后一行经常只是框架的退出提示,真正原因在前面几十行。
如果你还不熟悉服务器日志排查,可以顺手看这篇:VPS 日志怎么看:journalctl、systemctl、Nginx、应用日志怎么定位问题。
日志不一定完整,尤其是进程被系统杀掉时。
这时看 inspect:
docker inspect container_name --format '{{.State.ExitCode}} {{.State.OOMKilled}} {{.State.Error}}'
可能输出:
137 true
这就很明确了:容器被 OOM kill 过。
也可以看完整 State:
docker inspect container_name --format '{{json .State}}'
如果想格式化一点:
docker inspect container_name | less
重点字段:
ExitCode
OOMKilled
StartedAt
FinishedAt
Error
Health
几个常见退出码可以这么理解:
| ExitCode | 常见含义 |
|---|---|
0 | 程序正常结束,但容器不该结束 |
1 | 应用启动失败、配置错误、依赖失败 |
126 | 命令不能执行,权限或入口脚本问题 |
127 | 命令不存在,镜像或 command 写错 |
137 | 被 SIGKILL,常见于 OOM |
143 | 收到 SIGTERM,可能是正常停止 |
ExitCode=1 不要直接归因 Docker,先看应用日志。ExitCode=137 优先查内存。
很多 Compose 文件里会写:
restart: always
或者:
restart: unless-stopped
这不是坏事,生产环境通常需要它。但它会让你误以为“容器一直自己坏掉又起来”。
先看 restart 策略:
docker inspect container_name --format '{{.HostConfig.RestartPolicy.Name}}'
如果你正在排障,可以临时停掉重启循环:
docker update --restart=no container_name
docker stop container_name
然后手动启动一次:
docker start container_name
docker logs --tail=200 container_name
这样日志会清楚很多,不会一直刷屏。
如果是 Compose 管理的服务,改 docker-compose.yml 后再:
docker compose up -d
别直接手动改太多容器运行参数,最后容易和 Compose 文件不一致。
我自己遇到最多的 Docker 重启问题,第一类就是环境变量没传进去。
比如应用需要:
DATABASE_URL
REDIS_URL
SECRET_KEY
JWT_SECRET
APP_URL
但 .env 没放对位置,或者 Compose 没引用。
先看容器实际拿到了什么环境变量:
docker inspect container_name --format '{{range .Config.Env}}{{println .}}{{end}}'
如果你用 Compose,确认 .env 文件在执行 docker compose 的目录下。
常见坑:
environment:
DATABASE_URL: ${DATABASE_URL}
但当前 shell 或 .env 里根本没有 DATABASE_URL。
可以先看 Compose 渲染后的配置:
docker compose config
这条命令很有用,它能帮你看到变量最终有没有被替换。
如果输出里还是空值:
DATABASE_URL: ""
那容器重启就不奇怪了。
端口冲突有两种。
比如 Compose 写:
ports:
- "80:80"
但宿主机上 Nginx 已经占了 80。
查端口:
ss -lntp | grep ':80'
或者:
lsof -i :80
如果是 Docker 自己占了,也可以看:
docker ps --format 'table {{.Names}}\t{{.Ports}}'
有些应用默认监听 3000,你却以为它监听 80。
比如:
ports:
- "8080:80"
但应用实际在容器内跑 3000,那应该是:
ports:
- "8080:3000"
端口问题可以接着看这篇:VPS 端口被占用怎么办。
容器里应用启动时要读配置、写缓存、写日志,但宿主机挂载目录权限不对,也会直接退出。
比如日志里出现:
permission denied
read-only file system
cannot open config file
no such file or directory
先看挂载:
docker inspect container_name --format '{{json .Mounts}}'
再看宿主机目录:
ls -la /path/on/host
常见坑:
- 宿主机目录不存在,Docker 自动创建了空目录
- 文件 owner 是
root,但容器内应用用node、www-data、app用户运行 - 配置文件挂载成目录了
- 目录挂载到错误位置,把镜像内原有文件覆盖掉了
比如你本来想挂载文件:
volumes:
- ./config.yml:/app/config.yml
但本地 config.yml 不存在,某些情况下会变成目录,应用自然读不了。
如果你遇到 Docker 权限问题,可以看这篇:VPS 上 Docker Permission Denied 怎么办。
很多应用启动时要连数据库或 Redis。
你以为 Compose 已经写了:
depends_on:
- db
就万事大吉。其实不是。
depends_on 默认只保证启动顺序,不保证数据库已经 ready。
所以应用可能一启动就连数据库:
connect ECONNREFUSED db:5432
然后直接退出,Docker 再把它拉起来,形成重启循环。
更稳的做法是:
- 应用自己支持重试连接
- 给数据库加 healthcheck
- 应用启动脚本等待数据库 ready
例如 PostgreSQL:
services:
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
app:
depends_on:
db:
condition: service_healthy
如果你要把 Compose 用在生产环境,建议看这篇:VPS Docker Compose 生产环境怎么配。
有时候应用其实启动了,但容器还是不断重启。
这时要看是不是 healthcheck 和 restart 策略配合出了问题。
先看健康状态:
docker inspect container_name --format '{{json .State.Health}}'
或者:
docker ps
如果你看到:
Up 30 seconds (unhealthy)
说明主进程还活着,但健康检查失败。
常见错误:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
但镜像里根本没有 curl。
或者应用健康检查路径不是 /health,而是 /api/health。
还有一种:应用启动很慢,但 healthcheck 太急:
interval: 5s
timeout: 2s
retries: 3
应用还没起来,就被判定失败。
可以加 start_period:
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost:3000/health || exit 1"]
interval: 30s
timeout: 5s
retries: 3
start_period: 60s
如果是用反向代理访问,注意 healthcheck 在容器内部执行,localhost 指的是容器自己,不是宿主机,也不是 Nginx 容器。
如果退出码是 137,或者 inspect 里看到:
OOMKilled: true
先查内存。
看系统日志:
dmesg -T | grep -i 'killed process\|out of memory\|oom'
看容器资源:
docker stats
看 Compose 是否限制得太小:
mem_limit: 256m
很多 Java、Node、Python 应用在启动瞬间会吃一波内存。你给 256MB,看起来够跑,实际启动阶段就被杀。
处理思路:
- 给容器更合理的内存上限
- 给 VPS 加 swap 或 zram
- 降低应用并发和 worker 数量
- 检查是否有内存泄漏
- 把数据库、搜索服务等重服务拆出去
低内存 VPS 可以看这篇:VPS 低内存怎么用 ZRAM 和 Swap 优化。如果已经频繁 OOM,也可以看:VPS 内存不足? OOM 问题解决方案。
磁盘满时,容器可能不是立刻报“磁盘满”,而是表现为:
- 数据库启动失败
- 应用写缓存失败
- 日志写不进去
- overlay2 异常
- 容器刚启动就退出
先看磁盘:
df -h
再看 inode:
df -i
再看 Docker 占用:
docker system df
不要一上来就:
docker system prune -a
这条命令可能会删掉你还需要的镜像和构建缓存。先看清楚再清。
更稳的清理路线可以看这篇:VPS Docker 占满磁盘怎么办。
有些容器 Exited (0),不是失败,而是命令执行完了。
比如你写了:
command: npm run build
构建完成后进程退出,Docker 认为容器结束。如果你又写了:
restart: always
它就会一直重新构建、退出、再构建。
常驻服务应该是:
command: npm run start
或者镜像 Dockerfile 里要有正确的:
CMD ["node", "server.js"]
如果是前台/后台问题,也要注意。容器主进程必须以前台方式运行。你把服务丢到后台,主进程结束了,容器也会结束。
错误例子:
nginx &
正确思路:让主进程留在前台。
改了 .env、docker-compose.yml、挂载路径以后,只 docker restart 通常不够。
更稳的是:
docker compose up -d --force-recreate
如果镜像也改了:
docker compose up -d --build --force-recreate
如果你只是改了宿主机挂载进去的配置文件,要看应用是否支持热加载。不支持的话还是要重启对应容器:
docker compose restart app
但如果改的是环境变量、端口、volume 映射,通常要重新创建容器。
我一般按这个顺序走:
# 1. 看所有容器状态
docker ps -a
# 2. 看最近日志
docker logs --tail=200 container_name
# 3. 看退出码、OOM、错误状态
docker inspect container_name --format '{{.State.ExitCode}} {{.State.OOMKilled}} {{.State.Error}}'
# 4. 看 restart 策略
docker inspect container_name --format '{{.HostConfig.RestartPolicy.Name}}'
# 5. 临时停止重启循环
docker update --restart=no container_name
# 6. 手动启动一次,看第一条错误
docker start container_name
docker logs --tail=200 container_name
如果是 Compose:
docker compose ps -a
docker compose logs --tail=200 service_name
docker compose config
docker compose up -d --force-recreate
再按错误类型分流:
| 现象 | 优先查什么 |
|---|---|
Exited (1) | 应用日志、环境变量、配置文件、依赖服务 |
Exited (127) | command / entrypoint 是否写错 |
Exited (137) | OOM、内存限制、系统日志 |
permission denied | volume 权限、用户、文件 owner |
address already in use | 宿主机端口或容器内端口冲突 |
connect refused | 数据库/Redis 是否 ready、网络名是否正确 |
unhealthy | healthcheck 命令、路径、启动时间 |
容器从 Restarting 变成 Up 只是第一步。
还要继续看:
docker ps
docker logs --tail=100 container_name
docker stats
再从外部访问一次服务:
curl -I http://127.0.0.1:your_port
如果前面有 Nginx:
curl -I https://your-domain.com
最后建议把这些补上:
- 合理的
restart策略,不要所有服务无脑always - healthcheck 加
start_period - 日志限制,避免撑爆磁盘
- 关键服务加资源限制,但别限制得太死
- 重要数据卷定期备份
比如日志限制:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
生产环境 Compose 配置可以参考:VPS Docker Compose 生产环境怎么配。
如果这次是 Docker 容器一直重启,后面大概率还会遇到这些问题:
- VPS Docker Compose 生产环境怎么配
- VPS Docker 占满磁盘怎么办
- VPS 上 Docker Permission Denied 怎么办
- VPS 上 Docker 容器无法联网怎么办
最后提醒一句:遇到 Restarting,不要急着删容器、删 volume。先看日志、退出码、OOMKilled。很多时候你差的不是重装,而是找到第一条真正的报错。
