跳至内容
Docker 容器重启后无法启动故障处理

Docker 容器重启后无法启动故障处理

2026-10-01

服务器或 Docker 服务重启后,多个容器同时报 exec: "/bin/sh": no such file or directory,本文从现象判断、overlay2 与 containerd 排查,到 Dify 数据保全和恢复,给出完整处置流程。

1. 故障概述

1.1 故障现象

重启前容器运行正常,重启服务器或 Docker 服务后,容器无法启动:

docker compose up 时多个 Dify 容器报 /bin/sh 不存在

完整报错链路为 daemon → shim task → OCI runtime → runc,最终落在:

unable to start container process:
exec: "/bin/sh": stat /bin/sh: no such file or directory

同类报错还有 exec: "sh": executable file not found in $PATH 和 exec: "/bin/bash": stat /bin/bash: no such file or directory。

1.2 相关日志

Docker 日志中出现 containerd 侧的容器记录缺失:

cleanup: failed to delete container from containerd:
container "..." in namespace "moby": not found

1.3 受影响环境

项值
操作系统Linux
Docker 数据目录/data/docker
存储驱动overlay2
部署应用Dify
影响范围多个 Dify 容器,sh、/bin/sh、/bin/bash 均不可用

2. 故障判断

单个容器提示 /bin/bash: no such file or directory,通常只是镜像没装 Bash(例如 Alpine 只有 /bin/sh)。但本次 sh、/bin/sh、/bin/bash 在多个不同容器中同时失效,已经不是镜像自身的问题,而是容器根文件系统没有被正确挂载出来。

可能原因:

  1. Docker overlay2 文件层损坏;
  2. 镜像层内容丢失或为空;
  3. Docker 与 containerd 状态不一致;
  4. 数据目录所在磁盘曾经写满;
  5. 数据目录所在文件系统异常;
  6. 宿主机重启时 Docker/containerd 非正常退出;
  7. 数据目录被清理脚本、运维工具或人工操作改动;
  8. 镜像元数据尚在,但实际镜像文件已损坏。

3. 注意事项

排查前先明确三条红线,否则极易把可恢复故障变成数据事故。

3.1 不要直接删除 Docker 数据目录

禁止执行:

rm -rf /data/docker

该目录同时存放镜像、容器层和 named volumes,Dify 的 PostgreSQL、Redis、Weaviate 数据、用户上传文件、插件与模型数据都在其中。删掉即不可恢复。

3.2 不要直接删除 volumes

未备份前禁止执行:

docker volume prune
docker system prune -a --volumes

Dify 的数据库和业务数据保存在 volume 中,删除等同于清库。

3.3 不要单独移动 overlay2 目录

不要在 Docker 运行时执行 mv /data/docker/overlay2 ...。镜像元数据、容器配置与 overlay2 层三者相互引用,只搬走其中一份会导致:

  • 镜像元数据与实际层不一致;
  • 容器无法删除;
  • 镜像无法重新拉取;
  • Docker 与 containerd 状态进一步错乱。

确认存储目录严重损坏时,应走第 9 节的“备份数据卷后整体重建”流程。

4. 故障排查

4.1 确认数据目录与存储驱动

docker info | grep -iE 'Docker Root Dir|Storage Driver'

预期输出:

Docker Root Dir: /data/docker
Storage Driver: overlay2

再确认目录本身非空:

sudo ls -la /data/docker
sudo ls -la /data/docker/overlay2 | head
sudo ls -la /data/docker/volumes | head

4.2 检查磁盘与 inode

df -h /data
df -i /data

重点关注磁盘使用率或 inode 是否已达 100%、/data 是否正常挂载、所在磁盘是否被更换。磁盘或 inode 写满是镜像层损坏最常见的诱因,若已占满需先清理空间再继续。

4.3 检查容器日志体积

sudo du -sh /data/docker/containers/*/*-json.log 2>/dev/null \
    | sort -rh | head -20

日志过大时清空(只清空内容,不影响容器):

sudo find /data/docker/containers \
    -name '*-json.log' \
    -type f \
    -exec truncate -s 0 {} \;

4.4 检查文件系统异常

sudo dmesg -T | grep -iE \
'xfs|ext4|i/o error|readonly|remount|corrupt|overlay' \
| tail -50

同时检查服务日志:

sudo journalctl -u docker --since "2 hours ago" --no-pager
sudo journalctl -u containerd --since "2 hours ago" --no-pager

关注这些关键字:I/O error、Read-only file system、No space left on device、overlay、failed to mount、snapshot、container not found。

4.5 检查容器状态

docker ps -a

查看镜像与启动命令:

docker inspect 容器名 \
    --format 'Image={{.Config.Image}} Path={{.Path}} Args={{json .Args}}'

查看容器对应的存储层:

docker inspect 容器名 \
    --format '{{json .GraphDriver.Data}}'

4.6 验证镜像是否完整

绕过容器配置,直接用镜像起一个临时容器列目录:

docker image ls
docker run --rm --entrypoint /bin/ls langgenius/dify-api:latest /

镜像正常时应能看到完整根目录:

bin  dev  etc  home  lib  proc  root  run  tmp  usr  var

若命令无法执行,或根目录为空,说明镜像层已经损坏——这是区分“容器配置问题”和“存储层问题”的关键一步。

5. Docker 清理方案

如果排查结论是磁盘空间不足,按影响面从小到大清理。

5.1 查看空间占用

docker system df -v

5.2 清空容器日志

同 4.3,收益最大且无副作用,优先执行。

5.3 清理已停止的容器

docker container prune

5.4 清理悬空镜像

docker image prune

5.5 清理构建缓存

docker builder prune      # 仅清理未使用缓存
docker builder prune -a   # 清理全部构建缓存

5.6 清理无用网络

docker network prune

5.7 清理无用镜像(谨慎)

docker image prune -a

该命令会删除所有当前未被容器使用的镜像,包括本地构建、暂未启用的镜像,执行前确认这些镜像都能重新拉取或重新构建。

6. Dify 恢复方案

6.1 备份 Dify 配置

进入 Dify 项目目录(按实际路径调整):

cd /root/dify

sudo cp -a .env .env.bak.$(date +%F-%H%M%S)
sudo cp -a docker-compose.yaml \
    docker-compose.yaml.bak.$(date +%F-%H%M%S)

若使用了 docker-compose.middleware.yaml、docker-compose.prod.yaml 等额外文件,一并备份。

6.2 备份 Docker volumes

先确认卷列表和实际路径:

docker volume ls
docker volume inspect 卷名

再整体备份:

sudo cp -a /data/docker/volumes \
    /data/docker/volumes.bak.$(date +%F-%H%M%S)

空间不足时不要强行复制整个目录,但至少要单独备份 PostgreSQL 数据。

6.3 停止 Dify 容器

cd /root/dify
docker compose down --remove-orphans

docker compose down 不会删除 named volumes。切记不要加 -v 参数,也不要顺手执行 docker volume prune,这两个操作都会连同数据一起清掉。

6.4 清理残留容器

docker ps -a
docker rm -f 容器ID        # 删除确认无用的单个容器
docker container prune     # 删除所有已停止的容器

7. 重新拉取并重建容器

确认只是镜像内容异常时,删除镜像重新拉取即可。先列出 Dify 实际使用的镜像:

docker compose config --images

删除、重拉、强制重建:

docker rmi -f 镜像名
docker compose pull
docker compose up -d --force-recreate

确认结果:

docker compose ps
docker compose logs -f --tail=100

8. 重拉镜像后仍失败

如果重新拉取后仍然出现 exec: "sh": executable file not found in $PATH,或多个镜像都起不来,问题就不在镜像,而在存储层或文件系统。

8.1 确认 /data 挂载正确

findmnt /data
lsblk -f
df -hT /data

重点确认重启后 /data 没有挂到错误的磁盘或空目录上——挂载漂移会让 Docker 面对一个“看起来存在、实际上是空的”数据目录。

8.2 检查 overlay2 层内容

sudo du -sh /data/docker/overlay2
sudo find /data/docker/overlay2 -maxdepth 2 -type d -name diff | head

抽查若干 diff 目录的实际内容:

sudo find /data/docker/overlay2 \
    -maxdepth 2 \
    -type d \
    -name diff \
    -exec sh -c 'echo "DIR: $1"; ls -la "$1" | head' sh {} \;

大量 diff 目录为空,即可确认镜像层内容已经丢失。

8.3 检查 XFS 的 ftype 参数

/data 使用 XFS 时:

sudo xfs_info /data | grep -o 'ftype=[0-9]'

overlay2 要求 ftype=1。若输出 ftype=0,该文件系统不支持 overlay2,需要重新格式化或迁移数据目录到符合要求的文件系统。

9. 严重损坏时的重建方案

同时满足以下条件时,可以考虑重建 Docker 的镜像与容器存储:

  • 多个镜像都无法启动;
  • /bin/sh、/bin/bash 均不存在;
  • overlay2 层内容为空或损坏;
  • Docker 日志显示 containerd 状态异常;
  • 磁盘与文件系统检查已完成;
  • Dify 数据卷已完成备份。

9.1 执行顺序

核心原则是:不删除整个 /data/docker,而是整体改名保留现场。

  1. 备份 Dify 配置;
  2. 备份 Docker volumes;
  3. 确认数据库备份可用;
  4. 停止 Docker;
  5. 旧数据目录整体改名;
  6. 初始化新数据目录;
  7. 重新拉取镜像;
  8. 恢复或重新挂载数据卷。

停止服务并确认进程已退出:

sudo systemctl stop docker
sudo systemctl stop containerd
ps -ef | grep -E 'dockerd|containerd' | grep -v grep

改名旧目录并新建:

sudo mv /data/docker /data/docker.bak.$(date +%F-%H%M%S)
sudo mkdir -p /data/docker

确认 /etc/docker/daemon.json 中数据目录仍指向原路径:

{
  "data-root": "/data/docker"
}

启动并校验:

sudo systemctl start containerd
sudo systemctl start docker
docker info | grep -iE 'Docker Root Dir|Storage Driver'

重新拉取并启动 Dify:

cd /root/dify
docker compose pull
docker compose up -d

该方案会重建全部镜像和容器。旧目录中的 volumes 不会自动出现在新目录下,必须提前规划数据卷恢复方案。生产环境执行前务必完成 PostgreSQL、Redis 等数据备份。

10. 配置日志轮转

容器日志无限增长是撑满磁盘、进而损坏存储层的常见起点。编辑 /etc/docker/daemon.json,在原有配置基础上合并(不要整体覆盖):

{
  "data-root": "/data/docker",
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "50m",
    "max-file": "3"
  }
}

校验格式并重启:

python3 -m json.tool /etc/docker/daemon.json
sudo systemctl restart docker

该配置只对新创建的容器生效,Dify 容器重建后才会完全落地。

11. 预防措施

11.1 监控磁盘空间

对 /data 设置分级告警:80% 预警、90% 严重、95% 紧急处理。日常巡检:

df -h /data
df -i /data
docker system df

注意 inode 和容量要分别监控,inode 耗尽同样会导致写入失败。

11.2 固定镜像版本

避免长期使用 image: xxx:latest,改用固定版本 image: xxx:1.2.3。生产环境可进一步锁定 digest:

image: xxx@sha256:xxxxxxxx

这样镜像损坏后重新拉取,拿到的仍是同一个版本。

11.3 定期备份

至少覆盖 PostgreSQL、Redis、Weaviate、用户上传文件、Dify 配置文件和 Docker volumes,并定期验证备份可恢复。

11.4 不手动改动存储目录

/data/docker 下的 overlay2、image、containers、containerd 一律不要手工修改或删除,所有清理都通过 Docker 命令完成。

12. 处置顺序速查

# 1. 检查空间
df -h /data
df -i /data

# 2. 检查 Docker 占用
docker system df -v

# 3. 清理过大的容器日志
sudo find /data/docker/containers \
    -name '*-json.log' \
    -type f \
    -exec truncate -s 0 {} \;

# 4. 备份 Dify 配置和数据卷
cd /root/dify
sudo cp -a .env .env.bak.$(date +%F-%H%M%S)
sudo cp -a /data/docker/volumes \
    /data/docker/volumes.bak.$(date +%F-%H%M%S)

# 5. 停止并删除旧容器,但不要删除 volume
docker compose down --remove-orphans

# 6. 检查端口
sudo ss -lntp | grep ':3080'

# 7. 删除损坏的镜像
docker compose config --images
docker rmi -f 对应镜像

# 8. 重新拉取镜像
docker compose pull

# 9. 强制重新创建容器
docker compose up -d --force-recreate

# 10. 查看状态
docker compose ps
docker compose logs -f --tail=100

若重拉后仍无法启动,不要重复执行 docker compose up,转去检查底层:

df -h /data
df -i /data
sudo dmesg -T | tail -100
sudo journalctl -u docker --since "today"
sudo journalctl -u containerd --since "today"

排查重点是:磁盘空间、文件系统、overlay2 层、containerd 状态和数据目录挂载情况。

最后更新于