用 Docker Compose 在 NAS 上部署 Jellyfin,附硬件转码配置

Jellyfin 是一个开源的媒体服务器,把 NAS 里的电影、剧集、音乐整理成可以在手机、电视、浏览器上直接播放的流媒体库。本文记录我在飞牛 OS 上用 Docker Compose 部署它的过程,顺带聊聊硬件转码和权限这块的取舍。

为什么用 Docker 部署

直接装在系统里也不是不行,但媒体服务跑久了会涉及依赖升级、用户隔离、日志位置之类的问题。用容器隔离更干净,更新就是重新拉镜像,迁移到别的机器只要带走 compose 文件和配置目录。飞牛 OS 自带应用商店里的 Jellyfin 版本偏旧,这也是我选 Docker 的直接原因。

准备工作

下面几个条件需要先满足:

  • 已配置好 Docker 和 Docker Compose 环境
  • NAS 已开启 SSH 并能正常连接
  • 国内网络环境下,给 Docker 配好国内镜像加速,否则拉镜像会超时

如果还没装 Docker,以 Ubuntu 为例:

1
2
3
4
5
6
7
8
9
10
11
12
# 更新软件包列表
sudo apt update -y

# 安装 Docker
sudo apt install -y docker.io

# 安装 Docker Compose
sudo apt install -y docker-compose

# 启动 Docker 并设置开机自启
sudo systemctl start docker
sudo systemctl enable docker

注意 docker-compose(连字符)是 Python 实现的旧版 V1,已经不再维护。现在更推荐用 docker compose(空格)的 V2 插件,安装方式是 apt install docker-compose-plugin。两者命令大体兼容,本文示例两种都能用。

目录结构

我在 NAS 上单独建了一个目录放配置和缓存,和媒体数据分开。路径按你自己的实际情况调整,下面只是示例:

1
2
3
4
5
6
# 配置与缓存目录
mkdir -p /vol1/1000/test/jellyfin/config /vol1/1000/test/jellyfin/cache

# compose 文件目录
mkdir -p /vol1/1000/test/jellyfin/docker-compose
touch /vol1/1000/test/jellyfin/docker-compose/docker-compose.yml

config 存放 Jellyfin 的设置、元数据库、用户信息,需要持久化,丢了就得重新配置。cache 存放转码临时文件,删掉不影响配置。两者分开的好处是清理缓存时不会误伤配置。

编写 docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version: "3.8"

services:
jellyfin:
image: jellyfin/jellyfin
container_name: jellyfin
restart: unless-stopped
ports:
- "8096:8096"
volumes:
- /vol1/1000/test/jellyfin/config:/config
- /vol1/1000/test/jellyfin/cache:/cache
- /vol2/1000/media:/media/media
environment:
- TZ=Asia/Shanghai

几点说明:

  • 8096 是 Jellyfin 的默认 HTTP 端口,访问 http://NAS的IP:8096 进入初始化界面。
  • /config/cache 是容器内的固定路径,不要改,宿主机路径随意。
  • 媒体目录挂载到容器的 /media/media,之后在 Jellyfin 后台添加媒体库时填这个路径下的子目录。宿主机路径按你的媒体实际存放位置改。
  • 如果让 Jellyfin 把元数据和海报存在 config 目录(默认行为),媒体目录可以只读挂载,写成 /vol2/1000/media:/media/media:ro。只读挂载更安全,真要让它往媒体目录写刮削信息再改回来。
  • TZ=Asia/Shanghai 让容器内时间和日志用东八区,不加的话默认 UTC。
  • version: "3.8" 这一行在 Compose V2 里已经被忽略,留着不报错,删掉也行。

启动

进入 compose 文件所在目录:

1
sudo docker-compose up -d

查看运行状态:

1
sudo docker-compose ps

容器起来后,浏览器打开 http://NAS的IP:8096,跟着向导走完初始化,创建管理员账号,添加媒体库就能用了。

更新镜像用:

1
2
sudo docker-compose pull
sudo docker-compose up -d

硬件转码与 GPU 透传

这是部署 Jellyfin 最值得花时间想清楚的一块。

客户端能直接播放源文件格式时,Jellyfin 只做转封装,几乎不吃 CPU,这叫直通(direct play)。但当客户端不支持源编码,比如 H.265 视频在老电视上播放,Jellyfin 就得实时转码。CPU 软解 1080p 还扛得住,4K 就很吃力,风扇狂转。

有 Intel 核显或独立显卡的话,硬件转码效率高一个数量级。Docker 里要用上它,得把显卡设备透传进容器。

Intel 核显(VAAPI)的写法,在 compose 里加:

1
2
3
4
5
devices:
- /dev/dri:/dev/dri
group_add:
- video
- render

/dev/dri 是显卡设备节点。group_add 把容器进程加进 videorender 组,否则没权限访问设备。组名在不同发行版上可能不存在,更稳的写法是直接填 GID 数字,先用 getent group render video 在宿主机查到 GID 再填进去。

NVIDIA 显卡更麻烦,需要先在宿主机装 nvidia-container-toolkit,然后改用:

1
2
3
4
5
6
7
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]

容器起来后,去 Jellyfin 后台「播放,转码」里启用硬件转码,选对应的 VAAPI 或 NVIDIA NVENC。Intel 核显一般选 VAAPI,设备填 /dev/dri/renderD128

老实说,硬件转码能不能跑通很看驱动和内核版本。VAAPI 在某些飞牛 OS 内核上会缺 i915 驱动或固件,花半天调试不奇怪。如果只是自己看,片源都是客户端能直通的格式,软解也够用,别一上来就折腾 GPU。

权限与用户

官方 jellyfin/jellyfin 镜像默认以 root 身份运行容器进程。这意味着容器写出的 config、cache 文件在宿主机上是 root 所有,你用普通账号去删 cache 目录会提示权限不足。

想让它以普通用户跑,在 compose 里加 user: "1000:1000"(换成你实际的 UID:GID),并保证 config、cache 目录的所有者就是这个 UID。media 目录至少要给读权限。

媒体文件如果是 NAS 上其他账号管理的,要注意 UID 对得上,否则容器里读不到文件。这是 NAS 上跑容器最常见的坑。

几点局限

  • 这里只走了 HTTP,真要外网访问得套一层反向代理上 HTTPS,别直接把 8096 暴露到公网。
  • Jellyfin 的刮削器对中文电影名识别率一般,部分片源需要手动修正元数据。
  • 转码烧字幕时,容器里缺中文字体会显示方块,需要把字体挂进 /config/fonts

整体来说,Docker 部署 Jellyfin 本身不复杂,半小时能跑起来。真正费时间的是硬件转码和媒体库整理,这两块值得单独花时间。