Kavita 0.9.1.4 Docker Compose 部署指南
基于 Kavita 0.9.1.4 的 Docker Compose 全流程部署实录:官方镜像选型与自建镜像构建、数据目录与书库挂载、首次向导建管理员/建库扫描、OPDS 验证、exit 126 crash loop 排障实录与升级回滚(2026-10-31 实测)。
Kavita 0.9.1.4 Docker Compose 部署指南
> 目标:用 Docker Compose 把 Kavita 0.9.1.4 从零拉起——镜像选型、数据目录与书库挂载、首次向导(建管理员、建书库、触发扫描)、OPDS 验证与升级回滚,全部命令在本环境实测通过(2026-10-31)。组件定位与选型背景见 《Kavita 0.9.1.4 介绍与选型指南》。
一、环境信息
- 操作系统:Linux x86_64(内核 5.15,容器宿主机)
- Docker:29.9.2;Docker Compose:v5.6.1
- 软件版本:Kavita v0.9.1.4(官方 GitHub Releases 最新稳定版,2026-09-02 发布)
- 镜像:
- 官方镜像
kizaing/kavita:0.9.1.4(选型说明见二) - 本环境实测使用自建镜像
kavita-local:0.9.1.4(基于官方发行包kavita-linux-x64.tar.gz构建,构建过程与排障见三、十一)
- 官方镜像
- 端口:宿主
8145→ 服务5000 - 数据目录:
/opt/data/quzuo/tmp/kavita/deploy/config(挂载到/kavita/config) - 书库目录:
/opt/data/quzuo/tmp/kavita/media(挂载到/media,只读),内容为 2 个 CBZ 漫画、1 个 PDF、2 个 EPUB 的测试书库 - 测试账号:
admin / q1w2e3r4(首次向导创建)
二、镜像选型说明
2.1 官方镜像与 tag 策略
Kavita 官方(作者 kizaing)在 Docker Hub 发布镜像 kizaing/kavita,tag 分三类:
| tag | 用途 | 建议 |
|---|---|---|
latest | 最新稳定版 | 追新环境 |
nightly | 每夜构建 | 不建议生产 |
<版本号>(如 0.9.1.4) | 锁定版本 | 生产推荐,与 GitHub Release 版本号对应 |
生产环境建议用版本号 tag 固定版本,升级时显式改 tag 重拉,行为可控可回滚。
2.2 为什么本文用自建镜像实测
本环境到 Docker Hub 的拉取长时间停留在层下载阶段无法完成(断流),因此改用方案 B:官方发行包自建镜像。该方案直接使用 GitHub Release 的 kavita-linux-x64.tar.gz(2026-10-31 实测下载 117,675,081 字节),镜像内程序与官方镜像同源(同一发行包)。两条路线的 compose 文件仅 image: 一行不同,其余步骤完全一致。
2.3 自建镜像 Dockerfile(全文实测)
FROM ubuntu:22.04
COPY Kavita /kavita
RUN chmod 755 /kavita/Kavita
COPY seedconfig/appsettings.json /tmp/config/appsettings.json
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get install -y libicu-dev libgdiplus curl tzdata libjemalloc2 \
&& rm -rf /var/lib/apt/lists/*
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
EXPOSE 5000
WORKDIR /kavita
HEALTHCHECK --interval=30s --timeout=15s --start-period=30s --retries=3 \
CMD curl -fsS http://localhost:5000/api/health || exit 1
ENV DOTNET_RUNNING_IN_CONTAINER=true
ENV TZ=UTC
ENTRYPOINT [ "/bin/bash" ]
CMD ["/entrypoint.sh"]
要点:
RUN chmod 755 /kavita/Kavita不可省略:.NET自包含包解压出的主程序执行位与解压方式有关,丢了它容器会陷入 exit 126 循环(见十一、排障实录)。entrypoint.sh首次启动时把种子配置复制到/kavita/config/appsettings.json(仅当文件不存在时),这是seedconfig挂在/tmp/config的原因。libicu是 .NET 必需依赖,libgdiplus/libjemalloc2为官方镜像同款组件(图片处理与内存分配器)。
三、前置条件
- 宿主机已安装 Docker 与 Docker Compose(
docker compose version可用)。 - 规划两个目录:数据目录(读写)与书库目录(建议只读挂载)。
- 端口规划:Kavita 服务内部恒定监听 5000,对外映射宿主空闲端口(本文用 8145)。
- 书库目录按「库类型/系列/文件」组织。Kavita 把目录层级解读为 系列 → 卷 → 章,一个 Library 一种内容类型:
/media/
├── Comics/ # 漫画库(type=1 Comic)
│ ├── Planet Wanderer/
│ │ └── Planet Wanderer - Chapter 01/
│ │ └── Planet Wanderer - Chapter 01.cbz
│ └── Night Detective/
│ └── Night Detective Vol. 2/
│ └── Night Detective Vol. 2.cbz
└── Books/ # 图书库(type=2 Book)
├── Grace-field/
│ └── Field Notes.pdf
├── George Wells/
│ └── The Glass Horizon.epub
└── Ada Lane/
└── Signal Garden.epub
四、部署步骤
4.1 目录准备
mkdir -p /opt/data/quzuo/tmp/kavita/deploy/config # 数据目录(首次启动自动填充)
mkdir -p /opt/data/quzuo/tmp/kavita/media # 书库目录,放入你的漫画/电子书
4.2 docker-compose.yml(实测版本,逐行可抄)
name: kavita-0.9.1.4-stack
services:
kavita:
image: kavita-local:0.9.1.4 # 官方镜像环境改为 kizaing/kavita:0.9.1.4 即可
container_name: kavita-0.9.1.4-app
restart: unless-stopped
ports:
- "8145:5000"
volumes:
- ./config:/kavita/config # 数据目录(配置+数据库+缓存)
- /opt/data/quzuo/tmp/kavita/media:/media:ro # 书库目录(只读挂载)
networks:
- kavita-net
networks:
kavita-net:
external: true
name: kavita-0914-stack_kavita-net # 沿用既有网络;全新环境可改回普通 bridge 定义
⚠️ 注意:
networks段若用普通 bridge 定义并指定固定子网(如10.89.59.0/24),在宿主机上该网段池已被历史网络占用时会报Pool overlaps with other one on this address space。实测采用external: true沿用既有网络规避;全新环境直接写默认 bridge 即可。
4.3 启动与验证
cd /opt/data/quzuo/tmp/kavita/deploy
docker compose up -d
启动后观察日志,出现以下三行即代表服务就绪(实测回显):
[Kavita] [2026-10-31 04:06:55.930 +00:00 1] [Information] Kavita.Server.Program Starting with base url as /
[Kavita] [2026-10-31 04:06:55.967 +00:00 1] [Information] Microsoft.Hosting.Lifetime Now listening on: http://[::]:5000
[Kavita] [2026-10-31 04:06:55.968 +00:00 1] [Information] Kavita.Server.Program Kavita - v0.9.1.4
健康检查(容器内置 HEALTHCHECK 会轮询该端点):
curl http://127.0.0.1:8145/api/health
# Ok
docker compose ps 显示 Up (healthy) 即部署完成。
五、首次向导实测
浏览器打开 http://<宿主IP>:8145 进入初始化向导;以下用 API 等价完成(界面点击即这些请求)。
5.1 创建管理员
curl -s -X POST http://127.0.0.1:8145/api/Account/register \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"q1w2e3r4","email":"admin@quzuo.local"}'
实测回显(节选,token/apiKey 有删节):
{"id":1,"oidcId":null,"username":"admin","email":"admin@quzuo.local","roles":["Admin","Login"],
"token":"eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiYWRtaW4i…(删节)",
"apiKey":"tTyuDrTF…(删节)","preferences":{…},"kavitaVersion":"0.9.1.4",
"authKeys":[{"name":"opds","key":"tTyuDrTF…(删节)"},{"name":"image-only","key":"sMs3cdhv…(删节)"}]}
💡 说明:首个注册的用户自动成为管理员(roles 含
Admin)。注册响应直接返回 JWTtoken与 OPDS 用的apiKey;authKeys里opds与image-only两把 Key 分别用于 OPDS 订阅与图片直链。
5.2 建书库并触发扫描
创建 Library 需要完整 DTO(name/type/folders/fileGroupTypes/excludePatterns/metadataProvider),枚举取值实测如下:
| 字段 | 枚举值(实测) |
|---|---|
type(LibraryType) | 0=Manga,1=Comic,2=Book,3=Images,4=LightNovel |
fileGroupTypes(FileGroupType) | 1=Archive,2=Epub,3=Pdf,4=Images |
metadataProvider(有效值) | 2/3/4(0、1 会被 400 拒绝) |
TOKEN=<上一步登录或注册获得的 JWT>
curl -s -X POST http://127.0.0.1:8145/api/Library/create \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"name":"Comics","type":1,"folders":["/media/Comics"],"fileGroupTypes":[1],"excludePatterns":[],"metadataProvider":2}'
curl -s -X POST http://127.0.0.1:8145/api/Library/create \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"name":"Books","type":2,"folders":["/media/Books"],"fileGroupTypes":[2,3],"excludePatterns":[],"metadataProvider":3}'
# 手动触发一次全量扫描(force=true 表示强制重扫)
curl -s -X POST "http://127.0.0.1:8145/api/Library/scan?libraryId=2&force=true" \
-H "Authorization: Bearer $TOKEN"
# code=200
⚠️ 注意:
force是布尔参数,实测传force=1会报The value '1' is not valid.(400),必须写force=true。
扫描完成后的日志(实测节选,Debug 级):
[ScannerService] Beginning file scan on Books
[ScannerService] Library Books Step 1: Scan & Parse Files
[ScannerService] Step 1.C Found 3 directories to process for /media/Books
[ProcessFiles] Performing full scan on /media/Books/George Wells
[ProcessFiles] Performing full scan on /media/Books/Grace-field
[ProcessFiles] Performing full scan on /media/Books/Ada Lane
5.3 确认系列入库
curl -s -X POST "http://127.0.0.1:8145/api/Series/all-v2?page=1&pageSize=20" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{}'
实测回显(节选):
[{"id":5,"name":"Grace-field","pages":1,"format":4,…},
{"id":2,"name":"Night Detective","pages":3,"format":1,…},
{"id":1,"name":"Planet Wanderer","pages":4,"format":1,…}]
format 枚举实测:1=Archive(CBZ),4=Pdf。
六、OPDS 验证
用 5.1 拿到的 opds apiKey 直接订阅(无需 JWT):
curl -s "http://127.0.0.1:8145/api/opds/<apiKey>" | head -c 400
实测回显(节选):
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.w3.org/2005/Atom">
<updated>2026-10-31T04:30:50</updated>
<id>root</id>
<title>Kavita</title>
<icon>/api/opds/<apiKey>/favicon</icon>
<author><name>Kavita</name><uri>https://www.kavitareader.com</uri></author>
书库导航源:
curl -s "http://127.0.0.1:8145/api/opds/<apiKey>/libraries" | head -c 350
# <id>libraries</id><title>All Libraries</title> …
第三方阅读客户端(Mihon、Moon+ Reader 等)填 http://<宿主IP>:8145/api/opds/<apiKey> 即可订阅。关闭 OPDS 的开关在管理界面「系统设置」(对应 enableOpds 运行时设置,见 C1 篇)。
七、数据持久化说明
/kavita/config 是 Kavita 的状态重心,首次启动后自动生成(实测目录树):
/kavita/config/
├── appsettings.json # 启动参数(见 C1 篇逐项详解)
├── kavita.db / -shm / -wal # 主数据库(账户、书库、系列、进度)
├── cache.db / -shm / -wal # 缓存数据库
├── backups/ # 自动备份(每日任务生成)
├── bookmarks/ # 下载/书签保存位置
├── cache/ cache-long/ # 阅读缓存与长期缓存
├── covers/ # 封面图
├── logs/ # 运行日志(kavita<日期>.log)
├── themes/ fonts/ templates/ favicons/ images/ temp/
- 备份 = 备份
/kavita/config:账户、书库配置、阅读进度、封面全在里面。 - 书库内容文件是"被扫描对象",放只读挂载(
:ro)即可,Kavita 不写书库目录。 - 想彻底重置 Kavita:停服务后清空
/kavita/config再启动(等于全新安装),内容文件不受影响。
八、环境变量表
Kavita 的业务参数不走环境变量(走 /kavita/config/appsettings.json 与内置设置库,实测向进程注入 Port=5011 环境变量不生效,服务仍按既有配置绑定 5000,详见 C1 篇)。容器环境仅需关注:
| 环境变量 | 默认 | 说明 |
|---|---|---|
TZ | 官方镜像 UTC | 日志时区 |
DOTNET_RUNNING_IN_CONTAINER | true | .NET 运行时容器适配标记 |
LD_PRELOAD(entrypoint 自动设置) | jemalloc 路径 | 换用 jemalloc 分配器降低内存占用 |
九、等价 docker run
docker run -d \
--name kavita-0.9.1.4-app \
-p 8145:5000 \
-v /opt/data/quzuo/tmp/kavita/deploy/config:/kavita/config \
-v /opt/data/quzuo/tmp/kavita/media:/media:ro \
--restart unless-stopped \
kavita-local:0.9.1.4
十、常用运维命令与升级回滚
cd /opt/data/quzuo/tmp/kavita/deploy
docker compose ps # 状态与健康检查
docker compose logs -f --tail 100 kavita # 跟日志
docker compose restart kavita # 重启(改 appsettings.json 后执行)
docker compose down # 停止并移除(数据目录保留)
docker compose up -d --force-recreate # 镜像更新后强制重建
# 升级(官方镜像路线)
docker compose pull # 或改 image tag 后 up -d
docker compose up -d
# 升级前备份 / 回滚
tar czf kavita-config-$(date +%F).tgz ./config
docker compose down && tar xzf kavita-config-<日期>.tgz # 回滚数据目录
# 镜像回滚:image tag 改回旧版本号后 docker compose up -d --force-recreate
💡 说明:Kavita 启动时自动执行迁移(v0.9.1.4 启动日志含
ManualMigrateVersionCacheFiles等一串 migration,均标注 "This is not an error")。跨大版本升级前务必备份数据目录;跨版本升级后不可直接用旧版本回滚(数据库已前滚迁移),回滚需连同备份的数据目录一起还原。
十一、排障实录:exit 126 crash loop(自建镜像)
自建镜像首次启动后陷入 Restarting (126) 死循环,日志反复出现:
/entrypoint.sh: line 41: /kavita/Kavita: Permission denied
/entrypoint.sh: line 41: exec: /kavita/Kavita: cannot execute: Permission denied
Starting Kavita
11.1 定位过程
- exit 126 = 命令找到但不可执行,与 shebang/路径无关(路径
/kavita/Kavita明确存在)。 - 查看镜像内文件实际权限:
docker run --rm --entrypoint /bin/bash kavita-local:0.9.1.4 \
-c 'stat -c "%A %U:%G %s %n" /kavita/Kavita'
# -rw-r--r-- root:root 78256 /kavita/Kavita ← 644,无执行位
- 追查构建层(
docker history):COPY Kavita /kavita层存在,但RUN chmod +x /kavita/Kavita层缺失——实际构建时用的是没有 chmod 行的旧版 Dockerfile(发行包经第三方解压后主程序丢失执行位,Dockerfile 里的 chmod 是后来补写、未参与构建)。
11.2 修复
补一层 chmod 重新打 tag(全部构建层走缓存,秒级完成):
FROM kavita-local:0.9.1.4
RUN chmod 755 /kavita/Kavita && test -x /kavita/Kavita && ls -l /kavita/Kavita
docker build -f fix.Dockerfile -t kavita-local:0.9.1.4 .
docker compose up -d --force-recreate
重建后状态 Up (healthy),日志出现四、3 节的就绪三行。官方镜像 kizaing/kavita 不存在此问题(权限位由官方构建保证),本节适用于发行包自建镜像场景。
十二、常见问题
Q1:启动就退出,状态 Restarting (126)?
见十一排障实录:主程序缺执行位。检查镜像内 /kavita/Kavita 权限与构建层里是否真的执行了 chmod。
Q2:docker compose up 报 Pool overlaps with other one on this address space?
固定子网与既有网络冲突。改用 external: true 沿用既有网络(见 4.2),或换空闲网段。
Q3:创建书库报 400 missing required properties?
DTO 不完整。/api/Library/create 除 name/type/folders 外还要求 fileGroupTypes、excludePatterns、metadataProvider,枚举取值见 5.2;空 body 的实测回显为 JSON deserialization for type 'Kavita.Models.DTOs.UpdateLibraryDto' was missing required properties including: 'name', 'folders'.
Q4:书库扫描日志出现 Unable to parse any meaningful information out of file …epub?
该文件无法被解析器打开。实测用的手写最小 EPUB 即如此(首次还因 ZIP 本体截断,python3 读入报 BadZipFile: Truncated file header;重建合法 ZIP 后仍不被接受)。用 Calibre 等标准工具重新产出 EPUB 即可;CBZ/PDF 未出现此问题。
Q5:改了 appsettings.json 里的 Port 没生效?
实测 0.9.1.4 中运行期端口以设置库为准(GET /api/Settings 的 port 字段),appsettings.json 仅在首次初始化时作为初值:实测把文件改为 "Port": 5001 并重启后日志仍为 Now listening on: http://[::]:5000。改端口走管理界面「系统设置」或 Settings API。
Q6:健康检查路径是什么?
GET /api/health,正常返回文本 Ok(HTTP 200)。
十三、参考链接
- Kavita GitHub 仓库:https://github.com/Kareadita/Kavita (2026-10-31 查询)
- Kavita v0.9.1.4 Release:https://github.com/Kareadita/Kavita/releases/latest (2026-10-31 查询)
- 官方安装 Wiki(含 Docker 说明):https://wiki.kavitareader.com/ (2026-10-31 查询)
- Docker Hub 镜像页:https://hub.docker.com/r/kizaing/kavita (2026-10-31 查询)
- 站内选型篇:《Kavita 0.9.1.4 介绍与选型指南》
- 站内配置篇:《Kavita 0.9.1.4 配置参数详解》
- 站内 API 篇:《Kavita 0.9.1.4 REST API 使用详解》