Kavita 0.9.1.4 Docker Compose 部署指南

基于 Kavita 0.9.1.4 的 Docker Compose 全流程部署实录:官方镜像选型与自建镜像构建、数据目录与书库挂载、首次向导建管理员/建库扫描、OPDS 验证、exit 126 crash loop 排障实录与升级回滚(2026-10-31 实测)。

KavitaKavita漫画电子书媒体服务器自托管.NET2026/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 为官方镜像同款组件(图片处理与内存分配器)。

三、前置条件

  1. 宿主机已安装 Docker 与 Docker Compose(docker compose version 可用)。
  2. 规划两个目录:数据目录(读写)与书库目录(建议只读挂载)。
  3. 端口规划:Kavita 服务内部恒定监听 5000,对外映射宿主空闲端口(本文用 8145)。
  4. 书库目录按「库类型/系列/文件」组织。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)。注册响应直接返回 JWT token 与 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_CONTAINERtrue.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 定位过程

  1. exit 126 = 命令找到但不可执行,与 shebang/路径无关(路径 /kavita/Kavita 明确存在)。
  2. 查看镜像内文件实际权限:
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,无执行位
  1. 追查构建层(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)。

十三、参考链接

  1. Kavita GitHub 仓库:https://github.com/Kareadita/Kavita (2026-10-31 查询)
  2. Kavita v0.9.1.4 Release:https://github.com/Kareadita/Kavita/releases/latest (2026-10-31 查询)
  3. 官方安装 Wiki(含 Docker 说明):https://wiki.kavitareader.com/ (2026-10-31 查询)
  4. Docker Hub 镜像页:https://hub.docker.com/r/kizaing/kavita (2026-10-31 查询)
  5. 站内选型篇:《Kavita 0.9.1.4 介绍与选型指南》
  6. 站内配置篇:《Kavita 0.9.1.4 配置参数详解》
  7. 站内 API 篇:《Kavita 0.9.1.4 REST API 使用详解》