ryot Docker Compose 部署指南

在 Ubuntu 24.04 用 Docker Compose 部署 ryot v10.5.6 与 PostgreSQL 18,实测管理员注册、健康检查与 API 登录,含镜像选型、数据持久化、等价 docker run、运维命令与排障 FAQ。

ryotryotDocker部署Docker ComposePostgreSQL2026/11/03

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 Hubignisda/ryot官方主发布渠道
GitHub Container Registryghcr.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 特性更新
latestlatest始终最新主版本,跨大版本有破坏性变更风险
提交哈希sha- 前缀 + commit 短哈希按 commit 精确追溯

💡 技术拓展:ryot 采用周发版节奏(Releases 页面几乎每周有 v10.x.y 补丁),选型时「锁完整版本号」与「跟随主版本线」的取舍是——前者升级可控、后者自动获得修复。本文实际采用 v10.5.6,理由:与 PostgreSQL 18-alpine 组合已在测试环境完整验证,升级窗口由运维决定。

三、前置条件

  1. 已安装 Docker Engine 与 Compose v2+,实测回显:
docker --version
# Docker version 29.9.2, build a82e528
docker compose version
# Docker Compose version v5.6.1
  1. 宿主机 8141 端口未被占用(ss -lntp | grep 8141 无输出)。
  2. 创建工作目录:
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_URLpostgres://ryot:q1w2e3r4@ryot-db:5432/ryotPostgreSQL 连接串,必填
SERVER_ADMIN_ACCESS_TOKENq1w2e3r4创建首个管理员的凭证,必填

常用可选(全量参数以官方文档 https://docs.ryot.io/configuration 为准,逐项详解见本专栏配置参数篇):

环境变量默认值说明
TZEtc/GMT时区,建议设为 Asia/Shanghai
DISABLE_TELEMETRYfalse置 true 关闭 Umami 匿名使用统计
SERVER_GRAPHQL_PLAYGROUND_ENABLEDtrueGraphQL 交互查询页开关
SERVER_MAX_FILE_SIZE_MB70上传文件大小上限
USERS_ALLOW_REGISTRATIONtrue是否允许后续用户自助注册
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](前端)与后端进程日志分别定位。

十二、参考链接

ryot Docker Compose 部署指南 - Quzuo