ryot Docker Compose 部署指南
在 Ubuntu 24.04 用 Docker Compose 部署 ryot v10.5.6 与 PostgreSQL 18,实测管理员注册、健康检查与 API 登录,含镜像选型、数据持久化、等价 docker run、运维命令与排障 FAQ。
ryot Docker Compose 部署指南
目标:在 Ubuntu 24.04 服务器上以 Docker Compose 部署自托管媒体追踪系统 ryot v10.5.6(配套 PostgreSQL 18),完成管理员注册与 API 验证。本文全部命令与回显均已在
192.168.1.0/24内网服务器实测通过。
一、环境信息
- 操作系统:Ubuntu 24.04.5 LTS(x86_64)
- Docker Engine:29.9.2(build a82e528)
- Docker Compose:v5.6.1
- ryot:
ignisda/ryot:v10.5.6(GitHub 仓库 IgnisDa/ryot,GPL-3.0) - 数据库:
postgres:18-alpine(官方要求 PostgreSQL ≥ 15) - 对外端口:8141(宿主机映射到服务内部 8000)
- 数据目录:
/opt/data/quzuo/tmp/ryot/pgdata(bind mount) - compose 项目名:
ryot-v10.5.6-stack
二、镜像选型说明
ryot 官方镜像发布在两个仓库,任选其一:
| 仓库 | 地址 | 说明 |
|---|---|---|
| Docker Hub | ignisda/ryot | 官方主发布渠道 |
| GitHub Container Registry | ghcr.io/ignisda/ryot | 内容一致,网络受 Docker Hub 限制时可替换 |
镜像 tag 规则(实测 https://github.com/IgnisDa/ryot/releases.atom,2026-11-03 最新为 v10.5.6):
| tag 形态 | 示例 | 含义 |
|---|---|---|
| 完整版本号 | v10.5.6 | 精确锁定,生产推荐 |
| 次版本线 | v10.5 | 跟随 10.5.x 补丁更新 |
| 主版本线 | v10 | 跟随 10.x 特性更新 |
latest | latest | 始终最新主版本,跨大版本有破坏性变更风险 |
| 提交哈希 | sha- 前缀 + commit 短哈希 | 按 commit 精确追溯 |
💡 技术拓展:ryot 采用周发版节奏(Releases 页面几乎每周有 v10.x.y 补丁),选型时「锁完整版本号」与「跟随主版本线」的取舍是——前者升级可控、后者自动获得修复。本文实际采用
v10.5.6,理由:与 PostgreSQL 18-alpine 组合已在测试环境完整验证,升级窗口由运维决定。
三、前置条件
- 已安装 Docker Engine 与 Compose v2+,实测回显:
docker --version
# Docker version 29.9.2, build a82e528
docker compose version
# Docker Compose version v5.6.1
- 宿主机 8141 端口未被占用(
ss -lntp | grep 8141无输出)。 - 创建工作目录:
mkdir -p /opt/data/quzuo/tmp/ryot/pgdata
cd /opt/data/quzuo/tmp/ryot
四、部署步骤
4.1 编写 compose 文件
在 /opt/data/quzuo/tmp/ryot/docker-compose.yml 写入以下内容(数据库账号 ryot、密码 q1w2e3r4 为演示值,生产请替换):
name: ryot-v10.5.6-stack
services:
ryot-db:
container_name: ryot-v10.5.6-db
image: postgres:18-alpine
restart: unless-stopped
environment:
- POSTGRES_USER=ryot
- POSTGRES_PASSWORD=q1w2e3r4
- POSTGRES_DB=ryot
volumes:
- /opt/data/quzuo/tmp/ryot/pgdata:/var/lib/postgresql
networks:
- ryot-net
ryot:
container_name: ryot-v10.5.6-app
image: ignisda/ryot:v10.5.6
restart: unless-stopped
ports:
- "8141:8000"
environment:
- SERVER_ADMIN_ACCESS_TOKEN=q1w2e3r4
- DATABASE_URL=postgres://ryot:q1w2e3r4@ryot-db:5432/ryot
depends_on:
- ryot-db
networks:
- ryot-net
networks:
ryot-net:
driver: bridge
ipam:
config:
- subnet: 10.89.68.0/24
关键项说明:
DATABASE_URL与SERVER_ADMIN_ACCESS_TOKEN是官方文档标注的必填环境变量;前者指向 PostgreSQL,后者用于创建首个管理员账号。8141:8000:ryot 服务内部固定监听 8000(内置反向代理统一入口),对外端口按宿主机空闲情况调整。subnet: 10.89.68.0/24:显式网段避免与既有网络冲突,可按环境改。
4.2 启动并确认
cd /opt/data/quzuo/tmp/ryot && docker compose up -d
docker compose ps --format '{{.Name}} {{.Image}} {{.Status}} {{.Ports}}'
# ryot-v10.5.6-app ignisda/ryot:v10.5.6 Up 7 hours 0.0.0.0:8141->8000/tcp, [::]:8141->8000/tcp
# ryot-v10.5.6-db postgres:18-alpine Up 7 hours 5432/tcp
4.3 健康检查与入口验证
curl -s -o /dev/null -w "HTTP %{http_code}\n" http://127.0.0.1:8141/health
# HTTP 200
curl -s -o /dev/null -w "HTTP %{http_code} -> %{redirect_url}\n" http://127.0.0.1:8141/
# HTTP 302 -> http://127.0.0.1:8141/auth
/health 返回 200 表示内置反向代理、GraphQL 后端、前端三进程均已就绪;根路径 302 跳转到 /auth 登录页属于正常行为(未登录访问受保护页面时统一跳转认证页)。
4.4 创建管理员并登录
浏览器打开 http://<服务器IP>:8141/auth 注册,或用 API 完成(推荐,可脚本化)。注册首个管理员时必须携带与 SERVER_ADMIN_ACCESS_TOKEN 一致的 adminAccessToken:
curl -s http://127.0.0.1:8141/backend/graphql \
-H 'Content-Type: application/json' \
-d '{"query":"mutation { registerUser(input: {data: {password: {username: \"admin\", password: \"q1w2e3r4\"}}, adminAccessToken: \"q1w2e3r4\"}) { __typename ... on StringIdObject { id } ... on RegisterError { error } } }"}'
# {"data":{"registerUser":{"__typename":"StringIdObject","id":"usr_0MfPIsb1mhEh"}}}
成功返回 StringIdObject{id};若账号已存在则返回错误(见第十一章 FAQ)。登录换取 API 令牌:
curl -s http://127.0.0.1:8141/backend/graphql \
-H 'Content-Type: application/json' \
-d '{"query":"mutation { loginUser(input: {password: {username: \"admin\", password: \"q1w2e3r4\"}}) { __typename ... on ApiKeyResponse { apiKey } ... on LoginError { error } } }"}'
# {"data":{"loginUser":{"__typename":"ApiKeyResponse","apiKey":"UFPP7KKBIKI4P4NXZXVFX2Q2MNGWXODSEQ7TIOIKLWD7RNB6TVPQ===="}}}
将 apiKey 保存后,后续 API 调用统一携带请求头 Authorization: Bearer <apiKey>。
五、数据持久化说明
- ryot 的全部业务数据(用户、评论、观看记录、集合、缓存的元数据)都落在 PostgreSQL 中,本方案以 bind mount 方式持久化到宿主机
/opt/data/quzuo/tmp/ryot/pgdata(实测运行 7 小时后占用 72M)。 - ryot 进程本身无本地状态目录需要挂载;截图、上传文件等可选走 S3 对象存储(
FILE_STORAGE_S3_*环境变量),不启用则不落盘。 - 备份即备份数据库:
docker exec ryot-v10.5.6-db pg_dump -U ryot -d ryot > ryot_backup.sql
六、环境变量表
部署最小集(必填):
| 环境变量 | 本方案取值 | 说明 |
|---|---|---|
DATABASE_URL | postgres://ryot:q1w2e3r4@ryot-db:5432/ryot | PostgreSQL 连接串,必填 |
SERVER_ADMIN_ACCESS_TOKEN | q1w2e3r4 | 创建首个管理员的凭证,必填 |
常用可选(全量参数以官方文档 https://docs.ryot.io/configuration 为准,逐项详解见本专栏配置参数篇):
| 环境变量 | 默认值 | 说明 |
|---|---|---|
TZ | Etc/GMT | 时区,建议设为 Asia/Shanghai |
DISABLE_TELEMETRY | false | 置 true 关闭 Umami 匿名使用统计 |
SERVER_GRAPHQL_PLAYGROUND_ENABLED | true | GraphQL 交互查询页开关 |
SERVER_MAX_FILE_SIZE_MB | 70 | 上传文件大小上限 |
USERS_ALLOW_REGISTRATION | true | 是否允许后续用户自助注册 |
MOVIES_AND_SHOWS_TMDB_ACCESS_TOKEN | 空 | TMDB 令牌,影视元数据源 |
ANIME_AND_MANGA_MAL_CLIENT_ID | 空 | MyAnimeList 客户端 ID |
七、配置参数说明
ryot 没有外部配置文件,所有配置通过环境变量注入;进程启动后可通过免认证端点核对实际生效值(敏感值以 <empty>/**** 掩码展示):
curl -s http://127.0.0.1:8141/backend/config | python3 -m json.tool | head -30
# {
# "books": {
# "hardcover": {
# "api_key": "<empty>"
# },
# ...(输出节选,实测首尾)
# "server": {
# "backend_port": 5000,
# "backend_host": "0.0.0.0",
# "max_file_size_mb": 70,
# "admin_access_token": "****",
# "graphql_playground_enabled": true,
# "progress_update_threshold": 2,
# "disable_background_jobs": false
# },
实测回显中 server.backend_port: 5000 即 GraphQL 后端进程端口、graphql_playground_enabled: true 即查询页已开启。逐项含义见配置详解篇。
八、配置文件挂载
本方案唯一需要挂载的是数据库数据目录(见 compose 中 ryot-db.volumes)。ryot 服务内部自带一份反向代理配置(Caddyfile),打包在发行物内,可用以下命令查看以排障(只读,无需挂载):
docker exec ryot-v10.5.6-app cat /etc/caddy/Caddyfile
实测输出节选(路由规则):
:{$PORT:8000} {
handle_path /_i/* {
rewrite * /webhooks/integrations{path}
reverse_proxy {vars.backend_url}
}
handle_path /backend* {
reverse_proxy {vars.backend_url}
}
handle /health { ... }
reverse_proxy {vars.frontend_url}
}
即:/backend* 前缀请求转发 GraphQL 后端(剥前缀),/_i/* 转发媒体服务器 webhook,/health 返回聚合健康信息,其余交给前端页面。如需自定义,可通过环境变量 CADDY_TRUSTED_PROXIES、CADDY_FRONTEND_TARGET、CADDY_BACKEND_TARGET 覆盖模板变量,无需挂载文件。
九、等价 docker run
不用 compose 时,两条命令等价:
docker run -d --name ryot-db --network ryot-net \
-e POSTGRES_USER=ryot -e POSTGRES_PASSWORD=q1w2e3r4 -e POSTGRES_DB=ryot \
-v /opt/data/quzuo/tmp/ryot/pgdata:/var/lib/postgresql \
postgres:18-alpine
docker run -d --name ryot-app --network ryot-net -p 8141:8000 \
-e DATABASE_URL=postgres://ryot:q1w2e3r4@ryot-db:5432/ryot \
-e SERVER_ADMIN_ACCESS_TOKEN=q1w2e3r4 \
ignisda/ryot:v10.5.6
十、常用运维命令
# 查看状态
docker compose ps
# 查看实时日志
docker compose logs -f ryot
# 重启应用(不动数据库)
docker compose restart ryot
# 升级到新版本:改 compose 中 image tag 后
docker compose pull && docker compose up -d
# 停止并移除(数据目录保留)
docker compose down
升级后务必核对版本:
curl -s http://127.0.0.1:8141/backend/graphql -H 'Content-Type: application/json' \
-d '{"query":"query { coreDetails { version } }"}'
# {"data":{"coreDetails":{"version":"v10.5.6"}}}
十一、常见问题(FAQ)
Q1:向 /graphql 发查询返回 404?
实测复现:
curl -s http://127.0.0.1:8141/graphql -H 'Content-Type: application/json' \
-d '{"query":"query { __typename }"}'
# <!DOCTYPE html>...<h1>404 Not Found</h1>...(react-router 页面,日志见 No route matches URL "/graphql")
原因:/graphql 不是后端端点,它落到了前端路由。正确端点是 POST http://<服务器IP>:8141/backend/graphql(反向代理按 /backend* 前缀转发并剥前缀)。
Q2:登录报 USERNAME_DOES_NOT_EXIST?
{"data":{"loginUser":{"__typename":"LoginError","error":"USERNAME_DOES_NOT_EXIST"}}}
原因:ryot 没有「默认 admin 账号」,也不通过 SERVER_ADMIN_USERNAME/PASSWORD 环境变量建号;首次部署必须先执行 4.4 的 registerUser 创建管理员,SERVER_ADMIN_ACCESS_TOKEN 只在该首管创建时作为凭证校验。
Q3:注册报 IDENTIFIER_ALREADY_EXISTS?
{"data":{"registerUser":{"__typename":"RegisterError","error":"IDENTIFIER_ALREADY_EXISTS"}}}
原因:用户名已注册(管理员已存在)。直接改用登录流程即可;实测管理员存在后再次携带正确 adminAccessToken 注册,新用户仍为 NORMAL 角色(lot: "NORMAL"),ADMIN 角色仅在系统尚无管理员时由首管注册获得。
Q4:/health 返回 200 但页面打不开?
/health 聚合了后端配置输出,200 仅代表代理与后端就绪;前端异常时用 docker compose logs ryot 查看 [proxy](前端)与后端进程日志分别定位。