Audioserve Docker Compose 部署实战

Audioserve 0.30.1 Docker Compose 实测部署:镜像准备与目录规划、编排文件逐项解读、启动初始化回显、认证与 API/转码/章节链路冒烟、播放位置备份恢复、升级运维与四则排障实录。

AudioserveAudioserveDocker流媒体2026/10/28

Audioserve Docker Compose 部署实战

目标:在一台 Linux 服务器上用 Docker Compose 把 Audioserve(Rust 轻量有声书/音频流媒体服务器)跑起来,覆盖镜像准备、目录规划、编排文件、启动初始化、Web 与 API 冒烟、转码链路验证、播放位置备份/恢复与排障实录。组件背景与选型逻辑见《Audioserve 介绍与选型指南》,全部命令与回显来自 2026-10-28 真实服务器实测。

一、环境信息

实测环境与约定(真实服务器视角,宿主机为 Ubuntu,内核 7.0.0-38-generic):

项目取值
实测日期2026-10-28
宿主 DockerDocker version 29.9.2, build a82e528
镜像izderadicka/audioserve:latest(构建于 2026-05-23,amd64,237.9 MB,内部即 v0.30.1)
服务端口宿主 8138 → 容器 3000(Audioserve 默认监听 0.0.0.0:3000)
音频目录宿主 ./audio 以只读方式挂载为容器 /audiobooks
数据目录宿主 ./data 挂载为容器 /home/audioserve/.audioserve(缓存/密钥/进度落盘处)
Docker 网络compose 项目自定义网络,显式指定 10.89.57.0/24(避开宿主与其他项目已占网段)
示例凭据共享密钥 q1w2e3r4(生产请替换为不少于 14 字符的强密钥,官方安全基线要求)

Compose 编排文件名固定为 docker-compose.yml,项目名(顶层 name:)固定为 audioserve-0.30.1-stack。

二、软件与镜像准备

Audioserve 官方提供 Docker 镜像 izderadicka/audioserve,latest 标签即最新稳定构建,另有每日自动构建的 unstable 标签。拉取与镜像指纹确认:

docker pull izderadicka/audioserve:latest

回显(2026-10-28 实测):

latest: Pulling from izderadicka/audioserve
Digest: sha256:aac13420d5ef2cf322fdffecd49108708b82096d6a04b1db88ec7e7e9fc39fb3
Status: Downloaded newer image for izderadicka/audioserve:latest
docker.io/izderadicka/audioserve:latest

镜像关键事实(docker inspect 实测):

Created=2026-05-23T07:08:57.790941716Z  Size=237865772  Arch=amd64  OS=linux
Entrypoint=["./audioserve"]  Config.Cmd=null
RepoDigest=izderadicka/audioserve@sha256:aac13420d5ef2cf322fdffecd49108708b82096d6a04b1db88ec7e7e9fc39fb3
  • 工作目录 /audioserve,内含主程序 audioserve 与 Web 客户端目录 client/dist(官方 PWA 客户端已打进镜像,无需单独部署前端);
  • 内置 ffmpeg 8.0.1(转码开箱可用),镜像内运行用户为 uid 1000;
  • 入口点就是主程序本身、无默认 CMD,因此 compose 的 command 必须给出完整参数列表(含末尾音频目录 /audiobooks)。

三、目录规划与 Compose 编排

/opt/audioserve/
├── docker-compose.yml      # 编排文件
├── audio/                  # 音频库(按 作者/书名/章节 组织,挂载为只读)
│   ├── Clark, Arthur C./2001 A Space Odyssey/001 - Chapter 1.mp3 ...
│   ├── Clark, Arthur C./Rendezvous with Rama/CD1, CD2/
│   ├── Adams, Douglas/The Hitchhiker's Guide to the Galaxy/
│   └── Wells, H. G./The Time Machine/The Time Machine.m4b
└── data/                   # 服务数据(密钥/集合缓存/图标缓存/进度备份)

测试音频库用 ffmpeg 自造(正弦音调 mp3 与带章节元数据的 m4b),结构遵循官方 README 推荐的「作者 Last Name, First Name/书名/数字前缀章节文件」。关键前置:镜像内进程以 uid 1000 运行,宿主 data/ 目录必须对该 uid 可写,否则启动失败(排障实录第 1 条):

mkdir -p /opt/audioserve/data && chown 1000:1000 /opt/audioserve/data

docker-compose.yml 全文(实测版本):

name: audioserve-0.30.1-stack

services:
  audioserve:
    image: izderadicka/audioserve:latest
    container_name: audioserve-0.30.1-main
    restart: unless-stopped
    command: ["--tags", "--positions-backup-file", "/home/audioserve/.audioserve/positions-backup.json", "--positions-backup-schedule", "0 3 * * *", "/audiobooks"]
    environment:
      - AUDIOSERVE_SHARED_SECRET=q1w2e3r4
    ports:
      - "8138:3000"
    volumes:
      - ./audio:/audiobooks:ro
      - ./data:/home/audioserve/.audioserve
    networks:
      - audioserve-net

networks:
  audioserve-net:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 10.89.57.0/24

要点解读:

  • command:传入命令行参数。--tags 开启音频元数据标签展示;--positions-backup-file + --positions-backup-schedule 让播放进度每小时定期落盘 JSON(cron 表达式示例 0 3 * * * 为每天 03:00);末尾 /audiobooks 声明音频库集合;
  • 共享密钥用环境变量 AUDIOSERVE_SHARED_SECRET 注入而不写进命令行(避免出现在进程列表里);
  • 音频目录挂载为 :ro 只读——流服务器只需要读权限,写权限交给 data/;
  • 显式 name: 与自定义网络子网,避免多项目并存时 compose 自动命名与网段冲突。

四、启动与初始化

docker compose up -d

启动日志(docker logs audioserve-0.30.1-main,逐行照抄实测回显):

[2026-10-28T09:33:11Z INFO  audioserve] Started audioserve 0.30.1 #561370e (22.05.2026 01:40:12) with features async-tar, async-zip, behind-proxy, chrono, cron, default, folder-download, shared-positions, symlinks, tls, websock
[2026-10-28T09:33:11Z INFO  audioserve] Server listening on 0.0.0.0:3000
[2026-10-28T09:33:11Z INFO  collection::cache] Quick scan for collection "/audiobooks" finished

三行分别是版本与编译特性横幅(folder-download 目录打包下载、shared-positions 播放位置共享、tls 内置证书、websock 进度同步等均在列)、监听地址、集合快速扫描完成。按官方 Collections cache 章节说明:首次启动做全量扫描建立缓存,此后启动走基于已存元数据的快速核对,秒级就绪——与实测日志中的 Quick scan ... finished 一致。

docker compose ps 回显:

NAME                     IMAGE                           STATUS         PORTS
audioserve-0.30.1-main   izderadicka/audioserve:latest   Up 7 minutes   0.0.0.0:8138->3000/tcp, [::]:8138->3000/tcp

初始化完成后 data/ 目录内容(ls -la 实测,属主为容器内 uid 1000):

-rw-------  32 audioserve.secret      # 共享密钥的本地盐值哈希(服务端生成,600 权限)
drwxr-xr-x   3 col_db                 # sled 集合缓存数据库(实测约 540 KB)
drwxr-xr-x   4 icons-cache            # Web 客户端封面图标缓存
-rw-r--r--  171 positions-backup.json # 播放位置备份(配置了 --positions-backup-file 后出现)

五、Web 与 API 冒烟

Web 客户端:浏览器访问 http://<服务器IP>:8138/,输入共享密钥 q1w2e3r4 即进入音频库。curl 冒烟:

curl -s http://127.0.0.1:8138/ | head -3

回显:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    ...
    <title>Audioserve Web Client</title>

认证换 token:REST API 全部需要签名 token。客户端拿共享密钥换取 token——简单模式直接传明文密钥,官方推荐的防重放模式是加盐 SHA-256(客户端生成随机盐 + sha256(密钥+盐) 一并提交)。简单模式实测:

curl -s -w '\nHTTP %{http_code}\n' -X POST http://127.0.0.1:8138/authenticate \
  -H 'Content-Type: application/json' \
  -d '{"secret":"q1w2e3r4"}'

回显(token 为服务端签发的不透明随机串,实际约 176 字符,此处截断展示):

wTebv5s4icRVc4mWoTQX1s9FnIZ79tEAG7BwJGWmngwAAAAAbMLwuhvfO/aYka9z4NxBHDcsU5nDymzpjyCgX3XIl+uldnOv…
HTTP 200

token 有效期默认一年(--token-validity-days 可调),客户端把它放进 Authorization: Bearer 请求头或 audioserve_token cookie 即可。错误密钥实测(返回 401):

curl -s -w '\nHTTP %{http_code}\n' -X POST http://127.0.0.1:8138/authenticate \
  -H 'Content-Type: application/json' -d '{"secret":"AAAA|BBBB"}'

{"error":"Unauthorized","details":"Authentication failed"}
HTTP 401

带 token 访问集合元数据端点(后续所有 API 都带 Authorization: Bearer <token>,也可改用 cookie audioserve_token):

curl -s http://127.0.0.1:8138/collections/ -H "Authorization: Bearer $TOKEN"

回显(节选,data/ 实测值):

{
    "version": "0.30.1",
    "commit": "561370e (22.05.2026 01:40:12)",
    "folder_download": true,
    "shared_positions": true,
    "count": 1,
    "names": ["audiobooks"]
}

六、核心功能验证

目录浏览(API 是磁盘目录树的服务端视图,按集合编号寻址 /0/folder/<路径>,路径需 URL 编码):

curl -s 'http://127.0.0.1:8138/0/folder/Clark%2C%20Arthur%20C.%2F2001%20A%20Space%20Odyssey' \
  -H "Authorization: Bearer $TOKEN"

回显(实测):

{
    "is_file": false,
    "is_collapsed": false,
    "modified": 1793178392613,
    "total_time": 180,
    "files": [
        {"name": "001 - Chapter 1.mp3",
         "path": "Clark, Arthur C./2001 A Space Odyssey/001 - Chapter 1.mp3",
         "meta": {"duration": 60, "bitrate": 64, "tags": null},
         "mime": "audio/mpeg", "section": null},
        {"name": "002 - Chapter 2.mp3",
         "path": "Clark, Arthur C./2001 A Space Odyssey/002 - Chapter 2.mp3",
         "meta": {"duration": 60, "bitrate": 64, "tags": null},
         "mime": "audio/mpeg", "section": null},
        {"name": "003 - Chapter 3.mp3",
         "path": "Clark, Arthur C./2001 A Space Odyssey/003 - Chapter 3.mp3",
         "meta": {"duration": 60, "bitrate": 64, "tags": null},
         "mime": "audio/mpeg", "section": null}
    ],
    "subfolders": [],
    "cover": {"path": "Clark, Arthur C./2001 A Space Odyssey/cover.png", "mime": "image/png"},
    "description": {"path": "Clark, Arthur C./2001 A Space Odyssey/description.txt", "mime": "text/plain"},
    "tags": null
}

要点:total_time 是全书总时长(秒);files[].meta 由二进制内置 libavformat 解出(时长/码率/标签),与目录内第一张图片(封面)、第一个文本文件(简介)自动关联;子目录在 subfolders 中返回(CD1/CD2 这类即在此呈现)。

搜索(基于集合缓存的目录名搜索,/0/search?q=,files 与 subfolders 两组列表返回命中项,可含匹配目录内的文件):

curl -s 'http://127.0.0.1:8138/0/search?q=clark' -H "Authorization: Bearer $TOKEN"

回显(实测,q=clark 同一关键词命中 Clark, Arthur C. 与后补的 Clarke, Arthur C. New 两个目录,可见匹配为子串包含):

{
    "files": [],
    "subfolders": [
        {"name": "Clark, Arthur C.", "modified": 1793178391877, "path": "Clark, Arthur C.", "is_file": false, "finished": false},
        {"name": "Clarke, Arthur C. New", "modified": 1793179081450, "path": "Clarke, Arthur C. New", "is_file": false, "finished": false}
    ]
}

流式回放与转码(?trans= 三档默认 Opus in Ogg:l=32k / m=48k / h=64k):

P1="Clark%2C%20Arthur%20C.%2F2001%20A%20Space%20Odyssey%2F001%20-%20Chapter%201.mp3"
curl -s -o /dev/null -w 'raw   HTTP %{http_code} type=%{content_type} size=%{size_download}\n' \
  "http://127.0.0.1:8138/0/audio/$P1" -H "Authorization: Bearer $TOKEN"
curl -s -o /dev/null -w 'trans=l HTTP %{http_code} type=%{content_type} size=%{size_download}\n' \
  "http://127.0.0.1:8138/0/audio/$P1?trans=l" -H "Authorization: Bearer $TOKEN"
curl -s -o /dev/null -w 'trans=m HTTP %{http_code} type=%{content_type} size=%{size_download}\n' \
  "http://127.0.0.1:8138/0/audio/$P1?trans=m" -H "Authorization: Bearer $TOKEN"
curl -s -o /dev/null -w 'trans=h HTTP %{http_code} type=%{content_type} size=%{size_download}\n' \
  "http://127.0.0.1:8138/0/audio/$P1?trans=h" -H "Authorization: Bearer $TOKEN"

回显(60 秒 64k mp3 源文件,三种取流方式对比):

raw    HTTP 200 type=audio/mpeg size=480487
trans=l HTTP 200 type=audio/ogg size=253962
trans=m HTTP 200 type=audio/ogg size=475797
trans=h HTTP 200 type=audio/ogg size=607560

拖动续播依赖的 HTTP Range 实测(返回 206 分段):

curl -s -o /dev/null -w 'HTTP %{http_code} size=%{size_download}\n' -H 'Range: bytes=0-1023' \
  "http://127.0.0.1:8138/0/audio/$P1" -H "Authorization: Bearer $TOKEN"
HTTP 206 size=1024

有声书专项(同库实测):

  • .m4b 章节书:The Time Machine.m4b 的章节以 原始路径$$章节号$$起止$$后缀 形式呈现为虚拟文件,可按章取流(返回 audio/aac);
  • .chapters CSV 章节文件:003 - Long Story.mp3(25 分钟无章节 mp3)配同名 003 - Long Story.mp3.chapters CSV 后,目录接口会返回虚拟子文件夹呈现三个章节;
  • 整目录打包下载:/0/download/<目录路径> 返回 application/zip(实测 1.4 MB,测试库整体打包)。

七、播放位置备份与恢复

Audioserve 的"每设备听到哪了"存在 data/col_db 内嵌键值库中,多设备按"设备组"共享(/positions/<group> API,POST 写入、GET 读取、/last 取最新,实测写读均 200)。跨机迁移/备份走官方 JSON 备份机制,compose 中已配置:

command: ["--positions-backup-file", "/home/audioserve/.audioserve/positions-backup.json",
          "--positions-backup-schedule", "0 3 * * *", "/audiobooks"]

除定时任务外,可随时向进程发 SIGUSR2 强制立即备份(实测):

docker exec audioserve-0.30.1-main kill -USR2 1
cat data/positions-backup.json

回显(cat data/positions-backup.json,实测——顶层按集合路径分组,其下按目录路径、设备组嵌套):

{"/audiobooks":{"Clark, Arthur C./2001 A Space Odyssey":{"demo-group":{"file":"001 - Chapter 1.mp3","timestamp":1793179051575,"position":42.5,"folder_finished":false}}}
}

恢复:新机器上把 JSON 放回数据目录,启动时加 --positions-restore=v1。实测发现恢复流程是一次性的——服务导入完成即打印 Positions restoration is finished, exiting program, restart it now without --positions-restore arg 并退出;若 compose 里保留了该参数且配置了 restart: unless-stopped,容器会陷入"启动→恢复→退出→再启动"的循环。正确操作是:加上参数 docker compose up -d 等日志出现 restored/finished 字样 → 移除参数再 docker compose up -d。

整机备份清单(Docker 形态的全部状态就这三样):

  1. audio/ 音频库本体;
  2. data/ 服务数据(含 audioserve.secret——它是共享密钥的校验盐值,丢了会导致所有旧 token 失效,需重登录;迁移时随 col_db 一起拷贝);
  3. positions-backup.json 进度备份(可选,作为 col_db 的快照冗余)。

八、升级运维

  • 官方发版节奏为不定期 tag,镜像 latest 跟随稳定版;升级即 docker compose pull && docker compose up -d,数据目录格式向后兼容(col_db 为 sled,无手工迁移);
  • 想尝鲜换 image: izderadicka/audioserve:unstable(每日构建),回滚改回 latest 再 up -d;
  • 新增/修改音频目录无需重启:集合缓存默认开启 inotify 监听,变更约 10 秒防抖后自动进入索引(实测新建目录投递 mp3 后约 13 秒内可被搜索命中);大面积重组目录后可 docker compose restart 触发全量扫描;
  • 修改 compose 参数(如转码配置、端口)后 docker compose up -d 会自动重建容器,数据无损。

九、排障实录

以下四条均为本次实测中真实发生、真实定位的问题:

1. 启动即失败:Error creating/reading secret: Permission denied (os error 13)

现象:容器反复退出,日志只有一行 Error。原因:镜像内进程以 uid 1000 运行,宿主 data/ 目录若由 root 创建且未放权,uid 1000 无法写 audioserve.secret。定位与修复:

ls -ln data/   # 数字 UID 直观暴露属主问题
chown 1000:1000 data/ && docker compose up -d

日志中权限修复前后的真实对照(复现时逐行照抄):

[INFO  audioserve] Started audioserve 0.30.1 #561370e (22.05.2026 01:40:12) with features async-tar, ...
Error: Error creating/reading secret: Permission denied (os error 13)

同理,音频库只读挂载即可(无需写权限),但必须保证 uid 1000 可读:宿主侧 chmod -R o+r 或 chown -R 1000。

2. docker compose up 报 Bind for 0.0.0.0:8129 failed: port is already allocated

原因:宿主端口被其他进程/容器占用。实测复现的 daemon 报错原文:

docker: Error response from daemon: failed to set up container networking: driver failed programming external connectivity on endpoint ... Bind for 0.0.0.0:8138 failed: port is already allocated

处理:ss -ltnp | grep 8138 找到占用者后换宿主端口(只改映射左侧,如 "8139:3000"),或停掉占用进程。多项目共存时建议在规划期用 ss -ltn 核对端口表。

3. 认证 401:{"error":"Unauthorized","details":"Authentication failed"}

三类常见成因,按概率排序:密钥打错(共享密钥区分大小写);环境变量与客户端输入不一致(AUDIOSERVE_SHARED_SECRET 改动后客户端用旧密钥);时间偏差过大(token 为带过期时间的签名值,服务器时间漂移会提前判过期)。本次实测中前两类都复现过(错误密钥稳定 401),可用 /collections/ 端点快速二分——401 在认证层,404 在资源层。

4. 新放入的音频搜不到 / 目录不显示

集合缓存有约 10 秒防抖窗口(日志可见 with debounce 10 secs),投递新目录后稍等即可;若长时间未出现,优先检查 docker logs 中是否有目录扫描报错(如符号链接默认不跟随——需 --allow-symlinks),仍不行则 docker compose restart 触发全量重扫(缓存重建只读盘无风险)。

十、参考链接