5 月 7 日恢复一台 NAS 上的容器服务:Jellyfin、PhotoPrism、Home Assistant 都在同一个 compose 项目里。docker compose up -d 返回正常,docker compose ps 里大部分服务也是 running,但页面表现并不一致:Jellyfin 媒体库为空,PhotoPrism 新照片没有进入索引,Home Assistant 的部分设备状态不更新。
这类问题不适合只从"镜像有没有拉下来"切入。镜像入口是第一层,但 NAS 容器更容易卡在 bind mount、权限、端口和反代。下面是这次排查链路。
1. 先拆出恢复链路
我把 NAS 容器恢复拆成六层:
text
image -> compose -> volume -> permission -> port -> reverse proxy
对应的命令:
bash
docker compose ps
docker compose logs --tail=120
df -h
docker system df
如果磁盘满了,后面所有应用日志都可能误导人。PhotoPrism、Jellyfin 这类服务写缩略图、元数据和缓存时,对 Docker 目录和媒体目录都比较敏感。
2. 镜像入口先单独预检
节后服务恢复经常遇到一个问题:compose 一次拉多个镜像,任何一个源卡住,整组服务恢复都会被拖住。我的处理是把基础镜像拆开。
bash
docker pull docker.1ms.run/jellyfin/jellyfin:latest
docker pull docker.1ms.run/photoprism/photoprism:latest
docker pull ghcr.1ms.run/home-assistant/home-assistant:stable
docker pull docker.1ms.run/nginx:stable-alpine
毫秒镜像(1ms.run)在这一步承担的是多源镜像入口:Docker Hub 走 docker.1ms.run,GHCR 走 ghcr.1ms.run。它解决镜像拉取链路,后续的卷、权限和反代仍然要继续排。
预检通过后再跑:
bash
docker compose pull
docker compose up -d
3. compose 里最该先看的不是 image,而是 volumes
NAS 上 Docker 服务的长期稳定性,很多时候取决于 volume 写得够不够明确。
yaml
services:
jellyfin:
image: docker.1ms.run/jellyfin/jellyfin:latest
ports:
- "8096:8096"
volumes:
- /volume1/docker/jellyfin/config:/config
- /volume1/media:/media:ro
restart: unless-stopped
photoprism:
image: docker.1ms.run/photoprism/photoprism:latest
ports:
- "2342:2342"
volumes:
- /volume1/photos:/photoprism/originals
- /volume1/docker/photoprism/storage:/photoprism/storage
restart: unless-stopped
homeassistant:
image: ghcr.1ms.run/home-assistant/home-assistant:stable
network_mode: host
privileged: true
volumes:
- /volume1/docker/homeassistant:/config
- /etc/localtime:/etc/localtime:ro
restart: unless-stopped
这里有两个判断:
- Jellyfin 的媒体目录用
:ro,服务只读媒体文件,避免误写。 - PhotoPrism 的 originals 和 storage 分开,避免把索引存储和原始照片混到一个目录里。
4. bind mount 要看容器实际拿到的路径
Docker bind mount 是把宿主机路径挂进容器。这个设计很直接,也意味着服务和宿主机目录结构强绑定。
排查时不要只看 compose 文件,直接看运行中的容器:
bash
docker inspect jellyfin --format '{{json .Mounts}}'
docker inspect photoprism --format '{{json .Mounts}}'
再看宿主机目录:
bash
ls -lah /volume1/media
ls -lah /volume1/photos
ls -lah /volume1/docker/photoprism/storage
如果日志里有这些词,优先回到权限和路径:
text
permission denied
database is locked
cannot write
failed to scan directory
no such file or directory
NAS UI 里能访问目录,只代表当前登录用户能访问,不代表容器进程能访问。这个误判很常见。
5. 端口和反代分开测
Jellyfin 和 Home Assistant 这类服务,先测内网端口:
bash
ss -lntp
curl -I http://127.0.0.1:8096
curl -I http://127.0.0.1:8123
如果本机端口通,再测 NAS 局域网 IP。如果局域网 IP 通,再看反代。这个顺序可以避免把 Caddy/Nginx、证书、DDNS、路由器映射全部混在一起。
反代日志:
bash
docker logs --tail=120 nginx
docker logs --tail=120 caddy
我这次遇到的实际情况是:Jellyfin 容器本身没问题,8096 内网可访问,问题在反代 upstream 还指向了旧容器名。这个错误和镜像完全无关。
6. PhotoPrism 索引阶段要留出时间
节后导入照片和视频,PhotoPrism 的 CPU、内存、磁盘 IO 都会升高。不要看到缩略图没出来就重启容器。
bash
docker stats
docker compose logs --tail=200 photoprism
如果日志是索引、缩略图、导入相关记录,且没有持续权限报错,可以先等任务跑完。真正要处理的是 storage 不可写、originals 不可读、数据库连接异常。
7. 复盘
这次恢复最后确认了三类问题:
| 层级 | 现象 | 处理 |
|---|---|---|
| image | GHCR 镜像拉取慢 | 换成 ghcr.1ms.run 预检 |
| volume | Jellyfin 媒体库为空 | 修正 /volume1/media 挂载路径 |
| proxy | 域名访问 502 | 改 Nginx upstream 容器名 |
NAS Docker 服务恢复的关键不是"把镜像换个前缀就完事",而是用镜像预检排除第一层不确定性,然后继续看宿主机目录、权限、端口和入口。
下次我会把这套顺序固定到恢复文档里:
text
df -> docker pull -> compose ps -> inspect mounts -> logs -> ss -> proxy logs
它不复杂,但能避免在错误层级反复重启容器。