NAS Docker 服务恢复排查:卷权限、端口和反代

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

它不复杂,但能避免在错误层级反复重启容器。

相关推荐
牛奶1 小时前
1秒下单10万次,服务器是怎么扛住的?
大数据·服务器·后端
小强19881 小时前
为什么小程序中不能使用 window、document 或 jQuery?
后端
楼田莉子1 小时前
仿Muduo的高并发服务器:LoopThread模块及其ThreadPool模块
linux·服务器·c++·后端·学习
二月龙1 小时前
微信小程序页面栈限制解析与突破方案
后端
Rust研习社2 小时前
你为什么总是入门 Rust 失败
开发语言·后端·rust
SamDeepThinking2 小时前
批评下属不如当场展示解决方案
后端·程序员·团队管理
AskHarries2 小时前
GPT-Image-2(img2)到底能做什么?
后端
Leinwin2 小时前
GPT-5.5 Instant API接入教程:免费额度、速率限制与最佳实践
后端·python·flask
Xidaoapi2 小时前
从零搭建一个AI Agent:Python实战指南
后端