一个很具体的 NAS 问题。
我在一台 NAS 上用 Docker Compose 跑 Jellyfin,容器能启动,Web 页面也能打开,但是进后台添加媒体库后一直扫不出内容。目录明明在 NAS 上,电影和电视剧也都在,Jellyfin 里就是空的。
这类问题很容易被误判成"Jellyfin 不识别文件"或"刮削器有问题"。实际排下来,大多数时候不是 Jellyfin 本身,而是这几层出错:
- 宿主机路径和容器内路径混了。
- Compose 里的 volume 没挂进去。
- 容器用户没有读目录权限。
- NAS 重启后外接盘还没挂载,容器先启动了。
- 媒体库里填的是宿主机路径,而不是容器内路径。
下面按我的排查顺序写一遍。
环境
这次环境大概是这样:
| 项目 | 内容 |
|---|---|
| 设备 | NAS / 小主机都适用 |
| 系统 | Linux / NAS Docker 环境 |
| 服务 | Jellyfin |
| 启动方式 | Docker Compose |
| 现象 | 容器正常启动,媒体库为空 |
先说结论:Jellyfin 后台添加媒体库时,应该填写容器内路径,而不是 NAS 上看到的真实路径。
例如宿主机路径是:
bash
/volume1/media/movies
Compose 里把它挂到容器内:
yaml
volumes:
- /volume1/media/movies:/data/movies:ro
那么 Jellyfin 后台媒体库路径应该填:
bash
/data/movies
不是:
bash
/volume1/media/movies
这个坑非常常见。
先把 compose 写到最小可用
我会先把 Jellyfin 的 Compose 文件压到最小,不急着加反代、硬解、字幕、刮削和插件。
yaml
services:
jellyfin:
image: docker.1ms.run/linuxserver/jellyfin:latest
container_name: jellyfin
environment:
- PUID=1000
- PGID=1000
- TZ=Asia/Shanghai
volumes:
- ./config:/config
- /volume1/media/movies:/data/movies:ro
- /volume1/media/tv:/data/tv:ro
ports:
- "8096:8096"
restart: unless-stopped
这里用 docker.1ms.run/linuxserver/jellyfin:latest 是为了先排除镜像拉取这层变量。如果你团队内部已经有固定仓库,就替换成自己的镜像地址。本文重点不是换哪个镜像,而是把挂载、权限和扫描路径排清楚。
先启动:
bash
docker compose pull
docker compose up -d
docker compose logs -f jellyfin
如果这一步都卡住,就先别看媒体库。先把镜像拉取、容器启动和端口访问排完。
第一层:宿主机目录是否真的存在
先在 NAS 上确认目录存在:
bash
ls -lah /volume1/media
ls -lah /volume1/media/movies
这里不要只看图形界面。NAS 的文件管理器里看到目录,不代表 Docker 进程一定能看到同一路径。
尤其是外接硬盘、SMB 挂载、NFS 挂载、加密目录,经常会出现:
- Web 管理界面能看到。
- SSH 进去路径不同。
- Docker 启动时挂载点还没准备好。
如果宿主机上路径都不存在,Compose 写得再漂亮也没用。
第二层:容器内能不能看到文件
宿主机目录存在后,再进入容器看:
bash
docker compose exec jellyfin sh
ls -lah /data
ls -lah /data/movies
如果 /data/movies 为空,先不要怪 Jellyfin。
通常是 Compose volume 写错了,比如:
yaml
volumes:
- /volume1/movie:/data/movies:ro
但真实目录叫:
bash
/volume1/media/movies
也可能是中文目录、空格目录复制时少了一级。我的习惯是直接用 pwd 复制真实路径:
bash
cd /volume1/media/movies
pwd
再贴回 Compose。
第三层:媒体库路径不要填宿主机路径
这是最容易漏的一层。
Jellyfin 后台添加媒体库时,要填容器内路径:
bash
/data/movies
/data/tv
不要填:
bash
/volume1/media/movies
/volume1/media/tv
因为 Jellyfin 运行在容器里,它只知道 Compose 挂进去后的路径。宿主机路径对它来说不存在。
如果你填错路径,可能不会有特别明显的报错,只是扫描不到内容。
第四层:权限别一上来就 chmod 777
很多教程遇到权限问题会直接:
bash
chmod -R 777 /volume1/media
我不建议这么做,尤其是 NAS 上有家庭照片、备份和多用户目录时。
先看容器用什么 UID/GID:
bash
id
再看目录权限:
bash
stat -c "%u:%g %a %n" /volume1/media/movies
ls -lah /volume1/media/movies | head
如果使用的是 LinuxServer 风格镜像,PUID 和 PGID 要能读媒体目录。媒体目录至少要有读和进入权限:
bash
chmod -R a+rX /volume1/media/movies
chmod -R a+rX /volume1/media/tv
注意这里是 a+rX,不是 777。
配置目录可以给容器用户写权限:
bash
chown -R 1000:1000 ./config
媒体目录我通常只给读权限,避免容器误删或误改原始文件。
第五层:NAS 重启后的挂载顺序
还有一种很隐蔽的情况:NAS 重启后,Docker 比外接盘或远程挂载先起来。
结果就是:
- 容器启动成功。
/data/movies路径存在。- 但里面是空的。
这时要看挂载点:
bash
df -h
findmnt | grep media
mount | grep volume1
如果媒体盘还没挂上,先停止 Jellyfin:
bash
docker compose down
等存储挂载完成后再启动:
bash
docker compose up -d
如果经常发生,可以考虑把媒体盘挂载写进系统启动依赖,或者在 NAS 的任务计划里延迟启动容器。
第六层:硬解和媒体库为空不是一回事
很多人会把"媒体库为空"和"播放卡顿"混在一起。
媒体库为空,优先查:
- volume 路径。
- 容器内路径。
- 目录权限。
- 挂载顺序。
播放卡顿或转码失败,再查硬解:
bash
ls -lah /dev/dri
如果有核显设备,再考虑 Compose 里加:
yaml
devices:
- /dev/dri:/dev/dri
但这不是第一步。媒体库都扫不出来时,先别急着改硬解。
常见现象对照表
| 现象 | 优先排查 |
|---|---|
| Jellyfin 页面能打开,但媒体库为空 | 后台媒体库路径是否填成容器内路径 |
容器内 /data/movies 为空 |
Compose volume 宿主机路径是否写错 |
| 容器内能看到文件,但扫描不到 | 权限、文件名、媒体库类型 |
| NAS 重启后突然空库 | 外接盘或网络挂载是否晚于容器启动 |
| 能扫出来但播放卡 | 转码、硬解、网络带宽 |
docker compose pull 很慢 |
先做镜像源预检,避免把拉取问题误判成服务问题 |
我的最终检查顺序
以后再遇到 Jellyfin 空库,我会按这个顺序排:
bash
# 1. 宿主机目录
ls -lah /volume1/media/movies
# 2. Compose 是否生效
docker compose config
# 3. 容器内是否看得到
docker compose exec jellyfin ls -lah /data/movies
# 4. 权限
stat -c "%u:%g %a %n" /volume1/media/movies
# 5. 挂载点
df -h
findmnt | grep media
# 6. 日志
docker compose logs --tail=200 jellyfin
这套顺序比直接重装 Jellyfin 省时间。
小结
Jellyfin 媒体库为空,最常见的原因不是软件坏了,而是 Docker 的路径映射和权限边界没理清。
NAS 上跑容器服务,我现在会先把问题拆成三层:
- 镜像能不能稳定拉下来。
- 宿主机目录有没有正确挂进容器。
- 容器用户有没有权限读取。
第一层可以用毫秒镜像这类镜像入口先做预检;第二层和第三层必须回到 Compose、目录和权限本身。这样排下来,问题会清楚很多。