Audiobookshelf 2.37.2 Docker Compose 部署实战

Audiobookshelf 2.37.2 官方 Compose 部署全流程实测:四挂载点编排、PORT 默认 3333 与基础路径 /audiobookshelf 两大默认值避坑、init 建号、建库扫描、HLS 播放验证、SSRF 白名单接入自建播客源、首份备份与升级回滚,附 4 案例排障实录(2026-10-27 实测)。

AudiobookshelfDockerDocker ComposeAudiobookshelf自托管2026/10/28

Audiobookshelf 2.37.2 Docker Compose 部署实战

目标:在一台 Linux 服务器上,用官方 Compose 编排把 Audiobookshelf 2.37.2 从零部署到「初始化 root → 建立有声书库与播客库 → 扫描入库 → 播放验证 → 出第一份备份」的全链路可用状态。本文所有命令输入与输出均来自 2026-10-27 的真实部署实测(4 核 8 GB 虚拟机、Debian 系发行版、Docker Compose v2),包括一次「端口映射通了却连不上」的真实故障与两处极易踩坑的官方默认值(服务默认监听 3333、默认挂载基础路径 /audiobookshelf)。与本文配套的选型分析见《Audiobookshelf 2.37.2 介绍与选型指南》,参数级配置见《Audiobookshelf 2.37.2 配置参数详解》,API 与运维自动化见《Audiobookshelf 2.37.2 常用 API 与运维实战》。

一、环境信息

本篇为实测篇,全部数据来自一台真实 Linux 服务器的部署过程回显:

项目值
实测日期2026-10-27
服务器系统Linux(Debian 系,4 核 8 GB 虚拟机)
编排工具Docker Compose v2(docker compose version 实测可用)
部署产物官方发行镜像 advplyr/audiobookshelf:2.37.2(单服务编排)
服务版本2.37.2(官方最新稳定版,2026-10-23 发布)
宿主端口8137(ss -ltn 核实未占用后选定)
编排网络自建 bridge 网络 audiobookshelf-2.37.2-net,显式 --subnet 10.89.60.0/24
初始账号root / q1w2e3r4(实测创建)
媒体目录/opt/data/audiobooks(有声书,2 本书共 3 个音频文件)、/opt/data/podcasts(播客目录 + 本地 RSS 测试源)

💡 说明:编排与命令中的路径(如 /opt/data/...)是这台实测服务器的目录规划,读者部署时替换为自己的数据盘路径即可;本文保证的是「命令 → 回显」的真实性与可复现性。

二、部署方案总览

2.1 形态选择

Audiobookshelf 官方提供两种生产形态:Debian 系发行版的 apt 软件包(带 systemd 单元)与官方发行镜像(Compose 编排是官方文档的首选路径)。本篇选 Compose 形态,理由:升级回滚是「改一行版本号 + 一条命令」、数据目录边界清晰、便于与播客测试源等辅助服务组网。镜像标签策略上固定 2.37.2 而非 latest,保证本文回显可复现。

2.2 前置准备清单

  1. 端口:计划映射宿主 8137 → 服务端口 13378。先核实端口空闲:
ss -ltn | grep 8137 || echo "8137 free"

实测回显(端口空闲时只输出第二行):

8137 free
  1. 媒体目录:规划两个内容目录与两个数据目录,职责划分遵循官方约定:
挂载点用途读写要求
/audiobooks有声书媒体文件建议只读挂载(:ro),服务零侵入引用
/podcasts播客目录(订阅下载落地)读写(下载单集要写盘)
/config业务数据库(absdatabase.sqlite)+ 迁移脚本读写,必须持久化
/metadata元数据缓存、日志、转码流临时目录、备份产物读写,必须持久化

⚠️ 避坑:/config 存着全部用户、库结构与播放进度(SQLite 单库 absdatabase.sqlite),丢了它等于丢掉整站「记忆」;/metadata 下的 backups/ 是内置备份的落盘位置。两者都要落在明确持久化的宿主目录上。

  1. 网络:从空闲网段取一个显式子网(避免与既有网络冲突)。先查看已占网段:
docker network ls
docker network inspect <已有网络名> --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'

本例选定 10.89.60.0/24(规划前已核实 10.89.56–75 段内未被占用)。

三、编写编排文件

在部署目录(本例 /opt/data/audiobookshelf)创建 docker-compose.yml:

name: audiobookshelf-2.37.2-stack

services:
  audiobookshelf:
    image: advplyr/audiobookshelf:2.37.2
    container_name: audiobookshelf-2.37.2-main
    ports:
      - "8137:13378"
    volumes:
      - /opt/data/audiobookshelf/config:/config
      - /opt/data/audiobookshelf/metadata:/metadata
      - /opt/data/audiobooks:/audiobooks:ro
      - /opt/data/podcasts:/podcasts
    environment:
      - TZ=Asia/Shanghai
      - PORT=13378
    networks:
      - abs-net
    restart: unless-stopped

networks:
  abs-net:
    name: audiobookshelf-2.37.2-net
    driver: bridge
    ipam:
      config:
        - subnet: 10.89.60.0/24

四个关键点:

  1. PORT=13378 必须显式设置。这是本次实测踩到的最大的坑——官方镜像在未设置 PORT 时不监听 13378,而是落到服务源码的默认端口 3333(源码 index.js:const PORT = options.port || process.env.PORT || 3333)。如果照搬「8137:13378 的端口映射却不设 PORT」,映射会指向一个没有进程监听的端口,服务日志看起来一切正常但外部永远连不上。详见第八节案例 1。
  2. 服务默认工作在 /audiobookshelf 基础路径下。源码默认值 ROUTER_BASE_PATH ?? '/audiobookshelf',即根路径访问会由服务侧跳转到 /audiobookshelf/。局域网直连场景不需要改它,反代场景按《Audiobookshelf 2.37.2 配置参数详解》统一规划。
  3. 有声书目录挂载为 :ro(只读)。Audiobookshelf 对媒体文件是引用式管理,扫描与播放都不需要写媒体盘,只读挂载可以防误删。
  4. name: 指定编排名(audiobookshelf-2.37.2-stack),便于后续 docker compose 按名管理。

四、启动与初始化

4.1 启动

cd /opt/data/audiobookshelf
docker compose up -d
docker compose ps

实测回显:

[+] Running 2/2
 ✔ Network audiobookshelf-2.37.2-net  Created
 ✔ Container audiobookshelf-2.37.2-main  Started
NAME                          IMAGE                            STATUS         PORTS
audiobookshelf-2.37.2-main    advplyr/audiobookshelf:2.37.2    Up 4 seconds   0.0.0.0:8137->13378/tcp, [::]:8137->13378/tcp

启动日志(docker compose logs audiobookshelf | head)中的关键三行:

[2026-10-27 23:47:07.904] INFO: [Watcher] Initializing watcher for "有声书图书馆".
[2026-10-27 23:47:07.910] INFO: Listening on port :13378
[2026-10-27 23:47:07.915] INFO: [Watcher] "有声书图书馆" Ready

以及官方日志中的服务路径声明:

[2026-10-27 23:47:07.885] INFO: [Server] Serving from base path "/audiobookshelf"

4.2 健康检查

curl -s http://127.0.0.1:8137/ping

实测回显:

{"success":true}

再看初始化状态:

curl -s http://127.0.0.1:8137/status

实测回显(首次启动时 isInit 为 false,完成初始化后如下):

{"app":"audiobookshelf","serverVersion":"2.37.2","isInit":true,"language":"zh-cn","authMethods":["local"],"authFormData":{"authLoginCustomMessage":""}}

两个探针端点在根路径与基础路径下均可达(实测 /ping、/audiobookshelf/ping 都返回 200),用哪个做健康检查都可以。

4.3 初始化 root 账号

浏览器访问 http://<服务器IP>:8137,会跳入初始化向导:设置 root 用户名与密码(本例 root / q1w2e3r4),随后进入「新建库」页面。

API 等效操作(便于脚本化部署)——POST /init 创建 root:

curl -s -X POST http://127.0.0.1:8137/init \
  -H 'Content-Type: application/json' \
  -d '{"newRoot":{"username":"root","password":"q1w2e3r4"}}' \
  -w '\nHTTP %{http_code}\n'

实测回显:

{"user":{"id":"da78502d-603d-4a89-835e-48c2bd409761","username":"root",...}}
HTTP 200

登录拿访问令牌(后续 API 全部要用):

curl -s -X POST http://127.0.0.1:8137/login \
  -H 'Content-Type: application/json' \
  -d '{"username":"root","password":"q1w2e3r4"}' \
  | python3 -c "import json,sys; d=json.load(sys.stdin); print('user:', d['user']['username'], '| type:', d['user']['type'])"

实测回显:

user: root | type: root

⚠️ 避坑:root 只能初始化一次。对已初始化的服务重复调用 POST /init 会被拒绝,实测返回 HTTP 500,服务端日志记录 ERROR: [Server] attempt to init server when server already has a root user。数据目录复用时(迁移、重建编排)不会重新进入初始化向导,这是正常行为。

五、建库与扫描验证

5.1 建有声书库

用刚拿到的令牌创建「有声书」类型库,指向 /audiobooks(编排中已挂载,服务内路径):

TOKEN="<登录返回的 user.token>"
curl -s -X POST http://127.0.0.1:8137/api/libraries \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"name":"有声书图书馆","icon":"audiobook","mediaType":"book","provider":"audible","folders":[{"fullPath":"/audiobooks"}]}' \
  | python3 -c "import json,sys; d=json.load(sys.stdin); print('library:', d['name'], '| mediaType:', d['mediaType'], '| id:', d['id'])"

实测回显:

library: 有声书图书馆 | mediaType: book | id: d14e4bba-d24e-4e41-8761-ffb76851b4c1

创建库时服务端立即执行首轮扫描。实测目录里 2 本书被识别:

curl -s "http://127.0.0.1:8137/api/libraries/d14e4bba-d24e-4e41-8761-ffb76851b4c1/items" \
  -H "Authorization: Bearer $TOKEN" \
  | python3 -c "
import json,sys
d=json.load(sys.stdin)
print('total:', d['total'])
for it in d['results']:
    print('-', it['media']['metadata']['title'], '|', it['media']['metadata'].get('authorName'))
"

实测回显:

total: 2
- 三体系列:地球往事 | 刘慈欣
- 自托管实践指南(测试音频) | 演示作者

5.2 播放验证

对任一条目创建播放会话并请求 HLS 播放列表:

ITEM=f9b0678e-xxxx-xxxx-xxxx-xxxxxxxxxxxx   # 条目 id,来自上一步列表
curl -s -X POST "http://127.0.0.1:8137/api/items/$ITEM/play" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"deviceInfo":{"deviceId":"doc-demo"}}' > /tmp/play.json
SID=$(python3 -c "import json; print(json.load(open('/tmp/play.json'))['id'])")
curl -s -o /dev/null -w 'HLS playlist: %{http_code}\n' "http://127.0.0.1:8137/hls/$SID/output.m3u8" -H "Authorization: Bearer $TOKEN"

实测回显:

HLS playlist: 200

播放列表拉到 200,浏览器打开 http://<服务器IP>:8137 进入条目页点播放即可听到声音;手机 App 填同一地址与账号即可同步进度。播放会话的进度同步与关闭接口详见《Audiobookshelf 2.37.2 常用 API 与运维实战》。

5.3 播客库与本地 RSS 测试源(可选进阶)

播客库的建立与有声书库同型(mediaType 换成 podcast,目录挂 /podcasts)。播客的「订阅源」依赖服务端出网抓取 RSS——这里有一个实测踩到的硬性安全限制:服务端默认拒绝抓取解析到私网地址的订阅源(内置 SSRF 防护),并在 Web 端「添加播客」时直接报「Call to 10.89.60.2 is blocked」类错误。

局域网自建 RSS 源(如测试用、或内网播客分发)需要两步:

  1. 给测试源服务一个可解析的网络别名(订阅源 URL 里写的主机名必须能被服务端解析,短别名要显式注册,见第八节案例 4);
  2. 在编排中把该主机名加入官方提供的 SSRF 白名单环境变量(详见《Audiobookshelf 2.37.2 配置参数详解》):
    environment:
      - PORT=13378
      - SSRF_REQUEST_FILTER_WHITELIST=podcast-source

重启编排后,服务日志会出现白名单生效的确认行:

[2026-10-27 23:47:07.726] INFO: [Server] SSRF Request Filter Whitelisting: podcast-source

之后「添加播客 → 填订阅源 URL → 预检 → 建立」一路畅通,实测订阅本机 RSS 源并成功下载单集(/podcasts/<播客名>/第02期:媒体库命名规范.mp3 落盘,条目页出现已下载单集)。

六、第一份备份

初始化与建库完成后立即出一份备份,验证备份链路:

curl -s -X POST http://127.0.0.1:8137/api/backups -H "Authorization: Bearer $TOKEN" -w '\nHTTP %{http_code}\n'

实测回显(截取):

{"backups":[{"id":"2026-10-27T2356","key":"sqlite","backupDirPath":"/metadata/backups",...,"filename":"2026-10-27T2356.audiobookshelf","fileSize":17108,"serverVersion":"2.37.2"}]}
HTTP 200

备份文件落在了编排挂载的 /metadata/backups/ 下(宿主对应 /opt/data/audiobookshelf/metadata/backups/2026-10-27T2356.audiobookshelf,实测 17108 字节)。定时备份与保留份数在管理端「设置 → 备份」里配置,命令行改法见《Audiobookshelf 2.37.2 配置参数详解》。

七、版本升级与回滚

  • 升级:把 docker-compose.yml 里的镜像标签换成新版本号(如 2.37.2 → 之后的版本),依次执行 docker compose pull && docker compose up -d。数据库 schema 由服务内置迁移机制处理(/config/migrations/ 目录即迁移脚本,实测存在)。
  • 回滚:把标签改回旧版本再 up -d。注意官方不支持跨大版本的数据库「降级」迁移——回滚前先做一次手动备份(POST /api/backups),最坏情况用备份文件恢复 /config。
  • 升级前:先 curl http://127.0.0.1:8137/status 确认当前 serverVersion,升级完成后再查一次确认生效。

八、排障实录

以下 4 个案例全部来自本次部署实测,按「现象 → 定位 → 根因 → 处置」沉淀。

案例 1:端口映射正常,服务却连不上(PORT 默认值)

  • 现象:docker compose ps 显示 0.0.0.0:8137->13378/tcp 映射正常,启动日志无报错,但 curl http://127.0.0.1:8137/ping 无响应。
  • 定位:编排里没有显式设置 PORT 环境变量。查官方镜像源码 index.js:
const PORT = options.port || process.env.PORT || 3333;
const ROUTER_BASE_PATH = process.env.ROUTER_BASE_PATH ?? '/audiobookshelf';
  • 根因:Audiobookshelf 服务源码的默认监听端口是 3333 而非 13378。只写 8137:13378 映射时,宿主 8137 转发的 13378 上没有任何进程。
  • 处置:编排 environment 加 PORT=13378(或把映射改成 8137:3333,但官方文档的约定端口是 13378,建议跟随)。重启后日志出现 Listening on port :13378,探针立刻可达。
  • 教训:涉及「官方镜像 + 端口」时,以「映射端口 = 服务实际监听端口」双向核对为准,不要默认镜像行为与文档端口号一致。

案例 2:地址栏多了一层 /audiobookshelf(基础路径默认值)

  • 现象:启动日志明确输出 Serving from base path "/audiobookshelf",浏览器访问根路径时被服务侧引导到 /audiobookshelf/。
  • 根因:2.37.2 服务源码的默认基础路径是 /audiobookshelf(ROUTER_BASE_PATH ?? '/audiobookshelf'),这是官方默认行为而非故障。v2.37.2 发行说明同时明确:该变量自本版起自动规范化(补前导斜杠、去尾斜杠),写 audiobookshelf 或 /audiobookshelf/ 都会收敛为 /audiobookshelf。
  • 处置:局域网直连场景保持默认即可(书签直接用带路径的地址);需要根路径直达时,显式设置 ROUTER_BASE_PATH=/ 覆盖默认值,反代场景的完整配法见配置参数篇。

案例 3:添加私网播客订阅源被拦(SSRF 防护)

  • 现象:Web 端「添加播客」提交局域网 RSS 地址后报错,服务端日志记录 Call to 10.89.60.2 is blocked。
  • 定位:这是官方内置的 SSRF 请求过滤器在拦「解析到私网/环回地址」的出网请求,不是网络不通(从服务端 curl 测试源是通的)。
  • 处置:私网自建订阅源需要把源主机名写入 SSRF_REQUEST_FILTER_WHITELIST(逗号分隔多个)或以 DISABLE_SSRF_REQUEST_FILTER=1 整体关闭(不推荐,除非确信无 SSRF 风险)。实测加白名单并重启后,同一订阅源预检即通过(HTTP 200,返回解析出的标题与单集列表)。
  • 教训:「服务端能通」不等于「服务愿意出网」,遇到自建源被拦先查应用层的 SSRF 策略再查网络。

案例 4:订阅源主机名解析失败(EAI_AGAIN)

  • 现象:加了白名单后预检仍失败,服务端日志出现 getaddrinfo EAI_AGAIN podcast-source。
  • 定位:编排内自建的 RSS 源用了短主机名 podcast-source,但该名字没有注册进编排网络——网络内没有可解析的短别名,服务端 DNS 查询超时(EAI_AGAIN 是解析超时典型错误码)。
  • 处置:给源服务注册网络别名(Compose 里写 networks: [abs-net] 并加 aliases: [podcast-source],等效的命令行形式是 --network-alias podcast-source),重启后解析即通。实测解析成功后,同一 URL 的预检、订阅、单集下载全链路成功。
  • 教训:编排内服务互访时,URL 里的主机名必须与网络内注册的别名一致;用长名字(服务全名)通常天然可解析,短名要显式注册。

九、常见问题 FAQ

Q1:服务已经初始化过,重建编排后还要再跑 /init 吗? 不需要,只要 /config 挂载还是原来的目录,root 与库结构都在,直接登录即可。重复初始化会被拒绝(实测 HTTP 500)。

Q2:媒体文件放在挂载之外(宿主其他目录)能扫到吗? 扫不到。服务只能访问编排中声明的挂载路径,新建库时也只会列出 /audiobooks、/podcasts 这类已挂载目录。加目录 = 编排加挂载 + up -d 重建。

Q3:为什么推荐媒体目录只读挂载? 有声书库对媒体文件是纯引用式管理(读文件、封面与缓存写 /metadata),:ro 不影响功能,还能防止误操作改写原始音频文件。播客目录例外——订阅下载的单集要落盘,必须可写。

Q4:升级版本后页面打不开/端口不通了? 先看启动日志有没有 Listening on port 与版本号输出,再核对 PORT 与 ROUTER_BASE_PATH 两个环境变量是否被编排改动覆盖。这两个默认值是实测中最容易翻车的两处。

Q5:怎么验证备份可用? /metadata/backups/ 下出现 .audiobookshelf 文件即备份成功;恢复演练可在测试目录挂载空 /config 后按官方恢复流程导入备份文件,核对用户与库是否还原。

十、本篇小结

  • 官方 Compose 单服务编排 + 四挂载点(内容两目录 + /config + /metadata)是 Audiobookshelf 的标准部署形态;/config 与 /metadata 必须持久化。
  • 两个官方默认值必须心里有数:未设 PORT 时服务监听 3333;默认基础路径为 /audiobookshelf。
  • 部署闭环:up -d → /ping//status 探活 → POST /init 建 root → POST /api/libraries 建库扫描 → 播放会话 200 → POST /api/backups 出第一份备份。
  • 私网自建播客源 = 网络别名注册 + SSRF_REQUEST_FILTER_WHITELIST 白名单,二者缺一不可。

十一、参考链接