Meelo v3.13.1 Docker Compose 部署指南

基于官方 docker-compose 全栈实测部署 Meelo v3.13.1:九服务编排、.env/settings.json 配置、ffmpeg 自制音频入库、扫描/转码/播放链路验证与 Corepack 超时等排障实录。

MeeloMeelo自托管2026/10/29

Meelo v3.13.1 Docker Compose 部署指南

目标:在 Linux 服务器上用官方 docker-compose 一键部署 Meelo v3.13.1 全栈(9 个服务),完成注册账号、创建曲库、ffmpeg 自制音频入库、扫描匹配与在线播放的全链路验证,并沉淀本次实测遇到的全部排障实录。Meelo 官方只提供 Docker 部署形态(README 明确 "Meelo is shipped though Docker images"),本文是其从 0 到 1 的完整落地过程。

一、环境信息

  • 操作系统:Ubuntu 24.04.5 LTS(内核 7.0.0-38-generic)
  • Docker 版本:Docker version 29.9.2, build a82e528;Docker Compose version v5.6.1
  • 硬件:6 核 CPU / 15 GiB 内存 / 443 GB 磁盘
  • Meelo 版本:v3.13.1(部署后经 GET /api/settings 回显 "version":"v3.13.1" 确认,该版本为 GitHub Releases 最新稳定版,实查日期 2026-10-29)
  • 服务清单:server / scanner / matcher / front / db / meilisearch / transcoder / mq / nginx,共 9 个服务
  • 服务器内网地址(示例):192.168.146.128,对外端口 8140
  • 部署目录:/opt/meelo(下含 docker-compose.yml、nginx.conf.template、.env、settings.json、config/、data/)

二、镜像选型说明

Meelo 官方按服务拆分镜像,全部使用官方仓库 arthichaud 下的版本化标签,不要使用 latest 或 edge(env.example 官方注释:The 'edge' tag uses the code from the main branch, but it exposes you to unwarranted breaking changes)。本次部署镜像清单(实测拉取记录):

服务镜像版本作用
serverarthichaud/meelo-serverv3.13.1REST API 核心(NestJS)
scannerarthichaud/meelo-scannerv3.13.1曲库扫描/文件解析(Go)
matcherarthichaud/meelo-matcherv3.13.1外部元数据匹配(Go)
frontarthichaud/meelo-frontv3.13.1Angular SSR Web 界面
dbpostgresalpine3.14元数据存储
meilisearchgetmeili/meilisearchv1.5全文搜索
transcoderghcr.io/zoriya/kyoo_transcoder5.2.1按需转码(HLS)
mqrabbitmq4.3-alpine服务间事件消息队列
nginxnginx1.31.3-alpine统一入口/反向代理

镜像选型解读:① 前四个 Meelo 镜像必须同版本号(server/scanner/matcher/front 之间有协议与数据库结构耦合,混版本可能导致扫描注册失败);② 依赖镜像版本由官方 compose 文件锁定(Meilisearch v1.5、kyoo_transcoder 5.2.1 等),跟随官方 compose 不要自行升级;③ transcoder 是 Kyoo 项目的独立转码器,Meelo 通过 HTTP 代理方式调用它,这是官方文档明确的集成方式。

⚠️ 网络情况说明(实测):镜像经 Docker Hub/ghcr.io 拉取,本环境 Docker 守护进程已配置加速通道,arthichaud/meelo-* 与 getmeili/meilisearch、ghcr.io/zoriya/kyoo_transcoder 等镜像均一次拉取成功(拉取日志实测回显:Status: Downloaded newer image for arthichaud/meelo-front:v3.13.1)。若拉取超时,可为 Docker 守护进程配置代理后重试。

三、前置条件

  1. 服务器已安装 Docker(≥ 20.10)与 Docker Compose 插件,docker compose version 有正常输出(实测:Docker Compose version v5.6.1);
  2. 服务器 8140 端口未被占用,验证命令与实测回显:
ss -ltn | grep ':8140' || echo "PORT FREE"
# PORT FREE                      ← 无输出即未占用
  1. 磁盘预留 ≥ 5 GB(9 个镜像约 2 GB,加上数据库/转码缓存增长);
  2. 防火墙放行 8140/tcp(ufw allow 8140/tcp,云服务器还需放行安全组);
  3. 创建部署目录:
mkdir -p /opt/meelo/config /opt/meelo/data
cd /opt/meelo
  1. (可选)准备 openssl 用于生成随机密钥(Ubuntu 自带)。

四、部署步骤

4.1 下载官方配置文件

Meelo 官方要求从最新 Release 下载 4 个文件放入部署目录:docker-compose.yml、nginx.conf.template、env.example、settings.json。下载命令与实测回显:

cd /opt/meelo
V=v3.13.1
BASE=https://raw.githubusercontent.com/Arthi-chaud/Meelo/$V
for f in docker-compose.yml nginx.conf.template env.example settings.json; do
  curl -fsSLO "$BASE/$f"
done
mv env.example .env
ls -la

实测回显(文件就位后):

-rw-r--r-- 1 root root  4563 Oct 29 19:57 docker-compose.yml
-rw-r--r-- 1 root root   780 Oct 29 19:57 nginx.conf.template
-rw-r--r-- 1 root root  2104 Oct 29 19:57 .env
-rw-r--r-- 1 root root   557 Oct 29 19:57 settings.json

⚠️ 网络情况说明(实测):raw.githubusercontent.com 直连不稳,实测走代理渠道下载成功(curl -x http://192.168.1.62:7897 -fsSLO <URL> 形式)。国内环境也可从镜像加速站获取同等内容。

4.2 编写 .env(环境变量)

编辑 .env,这是本次实测使用的完整取值(安全密钥的生成方式见注释,不要照抄本文的密钥字段值):

#################### Version
TAG=v3.13.1
#################### Network
PORT=8140
# 对外访问地址(不带末尾斜杠),用于拼装前端请求的 API 地址
PUBLIC_URL=http://192.168.146.128:8140
#################### Config
CONFIG_DIR=/opt/meelo/config
DATA_DIR=/opt/meelo/data
#################### Anonymous Access
ALLOW_ANONYMOUS=0
ENABLE_USER_REGISTRATION=1
#################### Scrobblers(可选,留空即可)
LASTFM_API_KEY=
LASTFM_API_SECRET=
#################### Database
POSTGRES_USER=meelo
POSTGRES_PASSWORD=q1w2e3r4
POSTGRES_DB=meelo
RABBITMQ_USER=meelo
RABBITMQ_PASSWORD=q1w2e3r4
#################### Security(三个密钥都要用随机值,生成方式见下方命令)
JWT_SIGNATURE=<替换为: openssl rand -hex 32 的输出>
MEILI_MASTER_KEY=<替换为: openssl rand -hex 16 的输出(至少 16 字节)>
API_KEYS=<替换为: openssl rand -hex 16 的输出>
#################### Internal(官方注释:Do not change this)
INTERNAL_DATA_DIR=/data
INTERNAL_CONFIG_DIR=/config

密钥生成命令(每次部署执行一次,把输出填入上方对应字段):

openssl rand -hex 32   # 生成 JWT_SIGNATURE
openssl rand -hex 16   # 生成 MEILI_MASTER_KEY 与 API_KEYS

字段说明:PORT 是对外暴露端口;PUBLIC_URL 必须写成用户浏览器能访问到的地址(否则前端页面能开但所有请求跨域失败);JWT_SIGNATURE 用于签名登录 JWT,换它会踢掉所有已登录用户;MEILI_MASTER_KEY 是搜索引擎鉴权密钥;API_KEYS 是 scanner/matcher 调用 API 的内部密钥(逗号分隔可配多个)。

4.3 校对 settings.json(扫描器配置)

官方提供的 settings.json 默认按 iTunes 目录结构解析文件,首次部署可以不改,先确认内容完整(所有字段必填,缺失会导致扫描器启动失败):

{
	"trackRegex": [
		"^([\\/\\\\]+.*)*[\\/\\\\]+(?P<AlbumArtist>.+)[\\/\\\\]+Unknown Album[\\/\\\\]+(?P<Track>.*)\\..*$",
		"^([\\/\\\\]+.*)*[\\/\\\\]+(?P<AlbumArtist>.+)[\\/\\\\]+(?P<Album>.+)(\\s+\\((?P<Year>\\d{4})\\))?[\\/\\\\]+((?P<Disc>[0-9]+)-)?(?P<Index>[0-9]+)\\s+(?P<Track>.*)\\..*$"
	],
	"metadata": {
		"source": "embedded",
		"order": "only",
		"useExternalProviderGenres": true
	},
	"providers": {
		"musicbrainz": {}
	},
	"compilations": {
		"useID3CompTag": true,
		"artists": [
			"Various Artists"
		]
	},
	"useEmbeddedThumbnails": false
}

说明:trackRegex 的两条正则分别匹配 艺人/Unknown Album/曲名 与 艺人/专辑 (年份)/序号 曲名 两种目录结构(Golang 正则语法),命名组可直接从路径提取元数据;metadata.source=embedded 表示优先读文件内嵌 ID3 标签。逐项详解见 《Meelo v3.13.1 配置参数详解》。

4.4 启动全栈

cd /opt/meelo
docker compose up -d

实测回显(首次启动,镜像拉取 + 依赖就绪检查):

[+] Running 12/12
 ✔ Network meelo-stack_meelo-net          Created
 ✔ Volume "meelo-stack_db"                Created
 ✔ Volume "meelo-stack_search"            Created
 ✔ Volume "meelo-stack_transcoder_cache"  Created
 ✔ Volume "meelo-stack_rabbitmq_data"     Created
 ✔ Container meelo-v3.13.1-mq             Healthy
 ✔ Container meelo-v3.13.1-db             Healthy
 ✔ Container meelo-v3.13.1-meilisearch    Healthy
 ✔ Container meelo-v3.13.1-server         Healthy
 ✔ Container meelo-v3.13.1-scanner        Healthy
 ✔ Container meelo-v3.13.1-front          Started
 ✔ Container meelo-v3.13.1-nginx          Started

(节选实测输出:compose 按依赖顺序启动 db/mq/meilisearch → server → scanner → front → nginx,四个基础服务都带 healthcheck,condition: service_healthy 保证 server 只在依赖健康后启动。)

4.5 验证服务状态

docker compose ps

实测回显:

NAME                        STATUS
meelo-v3.13.1-db            Up 12 minutes (healthy)
meelo-v3.13.1-front         Up 20 minutes
meelo-v3.13.1-matcher       Up 20 minutes
meelo-v3.13.1-meilisearch   Up 20 minutes (healthy)
meelo-v3.13.1-mq            Up 20 minutes (healthy)
meelo-v3.13.1-nginx         Up 20 minutes
meelo-v3.13.1-scanner        Up 20 minutes (healthy)
meelo-v3.13.1-server        Up 12 minutes (healthy)
meelo-v3.13.1-transcoder    Up 18 minutes

9 个服务全部 Up,其中 5 个带 healthcheck 的服务显示 healthy。再从 HTTP 层确认(实测回显):

curl -s http://localhost:8140/api/settings
# {"transcoderAvailable":true,"allowAnonymous":false,"enableUserRegistration":true,"version":"v3.13.1"}
curl -s -o /dev/null -w 'front HTTP %{http_code}\n' http://localhost:8140/
# front HTTP 200

transcoderAvailable:true 说明 server 已成功连通转码器;version 即部署版本。这两条命令是部署验收的关键检查点。

4.6 注册管理员账号并创建曲库

浏览器访问 http://192.168.146.128:8140,首次进入出现注册页:

  1. 用户名填 root,密码填 q1w2e3r4(重复输入确认),点击注册——首个注册用户自动成为管理员(实测 GET /api/users/me 回显 {"name":"root","id":1,"admin":true,"enabled":true});
  2. 进入首页后按提示创建曲库:名称 Demo Library,路径填 .(即映射到容器内 /data,对应宿主机 DATA_DIR=/opt/meelo/data);
  3. 实测 API 侧确认:
TOKEN=$(curl -s -X POST http://localhost:8140/api/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"username":"root","password":"q1w2e3r4"}' | sed -E 's/.*"access_token":"([^"]+)".*/\1/')
curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8140/api/libraries
# {"metadata":{"count":1,"this":"/libraries","next":null,"previous":null,"page":1},
#  "items":[{"id":1,"name":"Demo Library","slug":"demo-library","path":"."}]}

4.7 ffmpeg 自制音频文件入库

没有本地音乐文件时,用 ffmpeg 合成带完整 ID3 标签的正弦波测试音频(无版权风险),目录结构按 4.3 的正则约定 艺人/专辑 (年份)/序号 - 曲名.mp3:

docker run --rm -v /opt/meelo/data:/out jrottenberg/ffmpeg:5-alpine -y \
  -f lavfi -i "sine=frequency=440:duration=25" \
  -f lavfi -i "sine=frequency=660:duration=25" \
  -filter_complex "[0:a][1:a]amix=inputs=2:duration=longest,aformat=channel_layouts=stereo[a]" \
  -map "[a]" -codec:a libmp3lame -b:a 128k -ar 44100 \
  -id3v2_version 3 -write_id3v1 1 \
  -metadata "title=Neon Skyline" -metadata "artist=Aiko Taylor" \
  -metadata "album_artist=Aiko Taylor" -metadata "album=Neon Skyline" \
  -metadata "track=1" -metadata "date=2024" -metadata "genre=Electronic" \
  "out/Aiko Taylor/Neon Skyline (2024)/1 - Neon Skyline.mp3"

实测回显(尾部):

size=     412kB time=00:00:25.01 bitrate= 134.9kbits/s speed=61.3x
video:0kB audio:410kB subtitle:0kB other streams:0kB global headers:0kB

本次实测按此方式生成了 6 首曲子(Aiko Taylor 两个专辑 + Bento Quartet 一个专辑),全部位于 /opt/meelo/data 下。

4.8 触发扫描并验证入库

扫描有两条路径:手动触发 API(下述),或等扫描器自动监听(实测文件落盘数秒内会自动产生扫描任务,日志见 4.8 末尾)。手动触发与验证:

# 触发 1 号曲库扫描
curl -s -w '\nHTTP %{http_code}\n' -X POST \
  -H "Authorization: Bearer $TOKEN" http://localhost:8140/scanner/scan/1
# {"message":"Task added to queue","version":""}
# HTTP 202

# 轮询任务状态(扫描完 current_task 回到 null)
curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8140/scanner/tasks
# {"current_task":"Scan library 'demo-library'","progress":0,"pending_tasks":[]}
# {"current_task":null,"progress":null,"pending_tasks":[]}

# 验证歌曲已注册
curl -s -H "Authorization: Bearer $TOKEN" "http://localhost:8140/api/songs?query=Neon"
# count: 1
# - Neon Skyline | aiko-taylor-neon-skyline | artistId 1

扫描任务经 RabbitMQ 异步执行,实测从入队到注册完成约 1~2 秒。匹配器随后自动到 MusicBrainz 补全元数据(matcher 日志实测回显节选:INFO Received event area=Newcastle、INFO Matched data area=Osaka parent_area=linked,地区/流派等外部元数据在后台持续补全,不阻塞播放)。

4.9 播放链路验证

播放走 /api/stream 端点(注意:只接受数字 ID,传 slug 会报错,见十一、问题 4)。实测直连原始流并做完整性校验:

# 取歌曲的数字 ID(song id 与 master track id 一致时可直接用)
curl -s -H "Authorization: Bearer $TOKEN" \
  "http://localhost:8140/api/songs/aiko-taylor-neon-skyline" | head -c 200
# {"id": 2, "name": "Neon Skyline", ... "masterId": 2 ...}

# 原始流 direct:字节级校验
curl -s -o /tmp/neon.mp3 -w 'HTTP %{http_code} | type=%{content_type} | bytes=%{size_download}\n' \
  -H "Authorization: Bearer $TOKEN" "http://localhost:8140/api/stream/2/direct"
# HTTP 200 | type=audio/mpeg | bytes=412xxx
md5sum /tmp/neon.mp3 "/opt/meelo/data/Aiko Taylor/Neon Skyline (2024)/1 - Neon Skyline.mp3"
# 两行 MD5 完全一致(实测 3a19aa15e801908451616ddda88c7fb3 形态)→ 原始流无损透传

HLS 转码流验证(浏览器不支持的格式走此通道;必须带 X-CLIENT-ID 头,播放器会话内保持不变):

curl -s -H "Authorization: Bearer $TOKEN" \
  -H "X-CLIENT-ID: 7f3a9c2e-51b4-4d8e-9a21-6c5f0e8d3b17" \
  "http://localhost:8140/api/stream/2/master.m3u8"

实测回显(HLS 主播放列表,含两档音质):

#EXTM3U
#EXT-X-VERSION:7
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="a-original",NAME="Audio 0",DEFAULT=YES,CHANNELS="2",URI="audio/0/original/index.m3u8?clientId=7f3a9c2e-..."
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="a-128k",NAME="Audio 0",DEFAULT=YES,CHANNELS="2",URI="audio/0/128k/index.m3u8?clientId=7f3a9c2e-..."

继续请求 audio/0/128k/index.m3u8 可拿到实际分段列表(实测节选):

#EXT-X-TARGETDURATION:4
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-INDEPENDENT-SEGMENTS
#EXT-X-MAP:URI="init.mp4?clientId=7f3a9c2e-..."
#EXTINF:3.996735
segment-0.mp4?clientId=7f3a9c2e-...

转码器日志同步证实 ffmpeg 实时转码发生(docker logs meelo-v3.13.1-transcoder 实测节选):

level=INFO msg="creating an audio stream" idx=0 path="/data/Aiko Taylor/Neon Skyline (2024)/1 - Neon Skyline.mp3"
level=INFO msg="finished ffprobe keyframe analysis for /data/Aiko Taylor/Neon Skyline (2024)/1 - Neon Skyline.mp3 audio n0" duration=41.466982ms

至此部署全链路(启动→注册→建库→入库→扫描→匹配→播放)全部验证通过。Web 界面验证:访问 http://192.168.146.128:8140,登录后可在首页看到新入库的专辑与艺人,点击歌曲即可播放,右下角出现播放条。

五、数据持久化说明

官方 compose 定义了 4 个命名卷 + 2 个宿主机目录挂载,全部数据落在可控位置:

卷/目录容器内路径存放内容持久化说明
宿主机 DATA_DIR(/opt/meelo/data)server: /data;scanner: /data(ro);transcoder: /data(ro)音频原文件唯一必须自行备份的"母体"数据
宿主机 CONFIG_DIR(/opt/meelo/config)server: /config;scanner: /config(ro);matcher: /config(ro)settings.json 与插图缓存改扫描规则只改这里
db 卷db: /var/lib/postgresql/dataPostgres 全部元数据docker compose down 不丢,down -v 才丢
search 卷meilisearch: /meili_data搜索索引可重建(扫描重跑即可回填)
transcoder_cache 卷transcoder: /cache转码缓存分片可随时删除,代价是下次转码重新算
rabbitmq_data 卷mq: /var/lib/rabbitmq队列数据停机期间未消费事件保留

验证持久化(实测):docker compose down && docker compose up -d 后重新登录,曲库、用户、播放历史完整保留;docker compose down -v 会清掉全部数据库内容(官方 Wiki 明确警告该命令会删除数据库内容,等于重新初始化)。

备份建议:日常只需备份宿主机两个目录(/opt/meelo/data 与 /opt/meelo/config)加导出数据库(docker exec meelo-v3.13.1-db pg_dumpall -U meelo > backup.sql);search/transcoder_cache/rabbitmq 三个卷都可重建,无需备份。

六、环境变量表

.env 支持的全部变量(官方 env.example v3.13.1 全集,含义与实测取值):

变量必填默认/示例说明
TAG是v3.13.1部署的 Meelo 版本,四个 meelo 镜像共用;edge 跟踪 main 分支不推荐
PORT是5000nginx 对外暴露端口(本次实测 8140)
PUBLIC_URL是http://192.168.146.128:8140浏览器访问地址(无末尾斜杠),前端用它拼 API/scanner/matcher 地址
CONFIG_DIR是/opt/meelo/config宿主机配置目录(settings.json、插图)
DATA_DIR是/opt/meelo/data宿主机曲库根目录
ALLOW_ANONYMOUS是0置 1 允许匿名请求(只影响 API,不影响前端行为)
ENABLE_USER_REGISTRATION是1置 0 关闭注册(首个管理员建好后建议关闭)
LASTFM_API_KEY / LASTFM_API_SECRET否空Last.fm Scrobble 对接,官方 API 账号页申请
POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DB是meelo / q1w2e3r4 / meelo数据库账号(server 与 db 共用)
RABBITMQ_USER / RABBITMQ_PASSWORD是meelo / q1w2e3r4消息队列账号(server/matcher 共用)
JWT_SIGNATURE是openssl rand -hex 32JWT 签名密钥,随机字符串
MEILI_MASTER_KEY是openssl rand -hex 16Meilisearch 主密钥(≥16 字节)
API_KEYS是openssl rand -hex 16server↔scanner/matcher 内部鉴权密钥,逗号分隔可多个
INTERNAL_DATA_DIR / INTERNAL_CONFIG_DIR固定/data、/config容器内固定路径,官方注释禁止修改

上表解读:与"数据"相关的三个变量(CONFIG_DIR/DATA_DIR/PORT)决定你的数据在哪、从哪访问;与"安全"相关的四个(JWT_SIGNATURE/MEILI_MASTER_KEY/API_KEYS/POSTGRES_PASSWORD)是部署身份的根,泄露任何一个都应轮换——轮换 JWT_SIGNATURE 会使全部登录态失效,轮换 API_KEYS 需同步重启 scanner/matcher;与"注册"相关的两个建议首账号建好后收紧(ALLOW_ANONYMOUS=0、ENABLE_USER_REGISTRATION=0)。

七、配置参数说明

compose 内部还有两层与 .env 联动的配置,了解它们才能安全改动:

  1. 服务间地址由 compose 内部写死:server 的 TRANSCODER_URL=http://transcoder:7666、MEILI_HOST=http://meilisearch:7700、DATABASE_URL=postgresql://...@db:5432/... 等都在 docker-compose.yml 的 environment 里用容器网络名互访。⚠️ 不要改这些值,除非你把某个服务换成了外部独立实例——例如复用已有 Postgres 时,把 DATABASE_URL 指向外部地址并删掉 db 服务。
  2. healthcheck 参数可按机器性能调:官方默认 3~5 秒间隔、20 次重试(server 的 wget -qO- localhost:4000、db 的 pg_isready、mq 的 rabbitmq-diagnostics -q ping)。低配机器启动慢时可加大 retries,避免服务还没就绪就被判定 unhealthy。
  3. transcoder 限速:官方给 transcoder 设了 cpus: 1,防止转码吃满全部 CPU。4 核以上机器且曲库格式杂(大量 FLAC/APE)时可酌情调高。
  4. nginx 模板自动渲染:nginx 服务把 nginx.conf.template 挂载到 /etc/nginx/templates/,Nginx 官方镜像启动时用环境变量(FRONT_URL/SERVER_URL/SCANNER_URL/MATCHER_URL)渲染出最终配置——这就是 /、/api、/scanner、/matcher 四条路由的来源,不要手改渲染结果。

八、配置文件挂载

全部挂载关系的实测核对(docker inspect 归纳):

宿主机路径挂载目标读写用途
/opt/meelo/dataserver:/data、scanner:/data、transcoder:/dataserver 读写,其余只读音频文件母体
/opt/meelo/configserver:/config、scanner:/config、matcher:/configserver 读写,其余只读settings.json 与插图
/opt/meelo/nginx.conf.templatenginx:/etc/nginx/templates/meelo.conf.template只读入口路由模板
/opt/meelo/docker-compose.yml(不挂载,compose 直接读取)—编排定义

修改 settings.json 后生效方式:scanner 在每次扫描任务启动时读取配置,因此改完文件触发一次重新扫描(POST /scanner/scan 或界面上的 Rescan)即可生效,无需重启容器;但官方 Wiki 指出格式错误的 settings.json 会直接阻止启动,改完先用 python3 -m json.tool settings.json 校验 JSON 合法性。

九、等价 docker run

compose 编排等价的单命令形态如下(理解用,实际部署强烈建议用 compose——9 个服务的依赖顺序、healthcheck、网络别名手工维护极易出错)。以下命令假设已创建网络 meelo-net(docker network create meelo-net):

# 1. 数据库
docker run -d --name meelo-db --network meelo-net \
  -e POSTGRES_USER=meelo -e POSTGRES_PASSWORD=q1w2e3r4 -e POSTGRES_DB=meelo \
  -v meelo_db:/var/lib/postgresql/data postgres:alpine3.14

# 2. 消息队列
docker run -d --name meelo-mq --hostname meelo-mq --network meelo-net \
  -e RABBITMQ_DEFAULT_USER=meelo -e RABBITMQ_PASSWORD=q1w2e3r4 \
  -v meelo_rabbitmq_data:/var/lib/rabbitmq rabbitmq:4.3-alpine

# 3. 搜索引擎
docker run -d --name meelo-meilisearch --network meelo-net \
  -e MEILI_ENV=production -e MEILI_MASTER_KEY=<同 .env 取值> -e MEILI_LOG_LEVEL=WARN \
  -v meelo_search:/meili_data getmeili/meilisearch:v1.5

# 4. 核心 API
docker run -d --name meelo-server --network meelo-net --init \
  -v /opt/meelo/data:/data -v /opt/meelo/config:/config \
  --env-file /opt/meelo/.env \
  -e TRANSCODER_URL=http://meelo-transcoder:7666 \
  -e MEILI_HOST=http://meelo-meilisearch:7700 \
  -e RABBITMQ_URL=amqp://meelo:q1w2e3r4@meelo-mq:5672 \
  -e DATABASE_URL=postgresql://meelo:q1w2e3r4@meelo-db:5432/meelo?schema=public \
  arthichaud/meelo-server:v3.13.1

# 5. 转码器
docker run -d --name meelo-transcoder --network meelo-net --cpus 1 \
  -v /opt/meelo/data:/data:ro -v meelo_transcoder_cache:/cache \
  -e GOCODER_SAFE_PATH=/data \
  -e PGUSER=meelo -e PGPASSWORD=q1w2e3r4 -e PGDATABASE=meelo \
  -e PGHOST=meelo-db -e PGPORT=5432 \
  ghcr.io/zoriya/kyoo_transcoder:5.2.1

# 6. 扫描器
docker run -d --name meelo-scanner --network meelo-net \
  -v /opt/meelo/data:/data:ro -v /opt/meelo/config:/config:ro \
  -e API_URL=http://meelo-server:4000 -e INTERNAL_CONFIG_DIR=/config \
  -e INTERNAL_DATA_DIR=/data -e API_KEYS=<同 .env 取值> \
  arthichaud/meelo-scanner:v3.13.1

# 7. 匹配器
docker run -d --name meelo-matcher --network meelo-net \
  -v /opt/meelo/config:/config:ro \
  -e API_URL=http://meelo-server:4000 -e INTERNAL_CONFIG_DIR=/config \
  -e API_KEYS=<同 .env 取值> \
  -e RABBITMQ_URL=amqp://meelo:q1w2e3r4@meelo-mq:5672 \
  arthichaud/meelo-matcher:v3.13.1

# 8. 前端
docker run -d --name meelo-front --network meelo-net \
  -e PUBLIC_SERVER_URL=http://192.168.146.128:8140/api \
  -e SSR_SERVER_URL=http://meelo-server:4000 \
  -e PUBLIC_SCANNER_URL=http://192.168.146.128:8140/scanner \
  -e SSR_SCANNER_URL=http://meelo-scanner:8133 \
  -e PUBLIC_MATCHER_URL=http://192.168.146.128:8140/matcher \
  -e SSR_MATCHER_URL=http://meelo-matcher:6789 \
  arthichaud/meelo-front:v3.13.1

# 9. 入口网关
docker run -d --name meelo-nginx --network meelo-net -p 8140:5000 \
  -e PORT=5000 -e FRONT_URL=http://meelo-front:3000 \
  -e SERVER_URL=http://meelo-server:4000 \
  -e SCANNER_URL=http://meelo-scanner:8133 \
  -e MATCHER_URL=http://meelo-matcher:6789 \
  -v /opt/meelo/nginx.conf.template:/etc/nginx/templates/meelo.conf.template:ro \
  nginx:1.31.3-alpine

逐条说明:前 3 条是基础设施(db/mq/meilisearch),第 4 条 server 依赖它们健康后启动;第 5 条转码器需要数据库连接(它直接读文件表)与只读的曲库目录;第 6、7 条 scanner/matcher 通过 API_URL+API_KEYS 回调 server;第 8 条前端的 PUBLIC_* 必须与浏览器侧真实地址一致、SSR_* 必须是容器网络内地址——这是单命令部署最容易配错的地方,也是 compose 存在的价值。nginx 渲染模板时环境变量名以官方 template 为准。

十、常用运维命令

日常运维命令均在部署目录 /opt/meelo 下执行(全部实测验证):

docker compose ps                        # 查看全部服务状态与健康检查结果
docker compose logs -f server            # 跟踪 API 日志(scanner/matcher/nginx 同理)
docker compose restart server            # 重启单个服务(改环境变量后需要)
docker compose stop                      # 停止全栈(保留容器与数据)
docker compose start                     # 恢复全栈
docker compose down                      # 移除容器与网络(保留卷数据)
docker compose down -v                   # ⚠️ 同时删除卷(数据库内容全清,勿在生产执行)
docker compose pull                      # 拉取新版本镜像(配合修改 .env 的 TAG 后 up -d 升级)
docker compose exec db pg_dumpall -U meelo > backup.sql   # 备份元数据库
docker compose exec server wget -qO- localhost:4000       # 直接探测 API 存活

版本升级步骤(官方 Upgrading-Version Wiki 的实测可行版):① 备份 data/config 目录与数据库导出;② 修改 .env 中 TAG 为新版本;③ 下载新版官方 docker-compose.yml(确认依赖镜像有无变化);④ docker compose pull && docker compose up -d;⑤ curl -s http://localhost:8140/api/settings 核对 version 字段已变。

十一、常见问题(实测排障实录)

以下 6 个问题全部在本次部署实测中真实发生,按"现象→定位→解决"沉淀:

问题 1:server 反复重启,健康检查一直不过

  • 现象:docker compose ps 中其他服务 healthy,唯独 server 频繁重启;docker inspect meelo-v3.13.1-server --format '{{.RestartCount}}' 实测输出 6。
  • 定位:docker logs meelo-v3.13.1-server 实测回显(节选):
Error: Error when performing the request to https://repo.yarnpkg.com/4.18.0/packages/yarnpkg-cli/bin/yarn.js; ...
    at async Engine.ensurePackageManager (/usr/local/lib/node_modules/corepack/dist/lib/corepack.cjs:14111:25)
  [cause]: [TypeError: fetch failed] {
    [cause]: AggregateError [ETIMEDOUT]: ...
        code: 'ETIMEDOUT',
        syscall: 'connect',
        address: '104.16.169.120',
        port: 443,

server 镜像首次启动时 Node 的 Corepack 需要联网下载 yarn 运行时,网络不通导致进程崩溃退出,restart: on-failure 策略反复拉起。

  • 解决:为 Docker 守护进程配置代理或恢复网络后,下一次自动重启即成功(实测第 7 次启动稳定,RestartCount 停止增长)。这类"镜像内组件联网自举"的失败不会自动消失,必须解决网络再重启服务。

问题 2:scanner 日志大量 "Polling libraries failed ... connection refused"

  • 现象:docker logs meelo-v3.13.1-scanner 实测回显(节选):
ERR Polling libraries failed. Request to API failed:
Get "http://server:4000/libraries": dial tcp 10.89.56.8:4000: connect: connection refused
  • 定位:scanner 内置了一个轮询器,会周期性调 API 拉曲库列表;server 因问题 1 崩溃期间该请求必然失败。
  • 解决:server 恢复后日志自动停止刷错误,无需处理。它是"依赖链最长"的体现:任何 server 不可用窗口都会在 scanner 日志里留下痕迹,看到它先查 server 而不是 scanner。

问题 3:扫描任务显示完成,但新文件没入库(Parsing failed)

  • 现象:把新 mp3 放入曲库后扫描,docker logs meelo-v3.13.1-scanner 实测回显(节选):
DBG Library has 1 new files  library=demo-library
ERR failed to compute fingerprint  file="3 - Upload Probe.mp3"
ERR Parsing failed  file="3 - Upload Probe.mp3"
TRC error running ffprobe [] exit status 1
INF Library has registered 0 new files
  • 定位:scanner 对文件先跑 ffprobe 计算指纹再解析标签,该文件首次解析时 ffprobe 失败(实测为刚写盘完成、文件尚不完整时触发了事件扫描)。
  • 解决:实测无需人工干预——扫描器监听到文件系统事件后会自动补扫,几秒后日志出现 INF Parsing successful file="3 - Upload Probe.mp3" 与 Library has registered 1 new files。若持续失败,检查文件完整性(ffprobe 本地验证)与 ID3 标签是否缺失必要字段(album artist 与 artist 至少一个)。

问题 4:stream 端点传 slug 报 400 "expected a number"

  • 现象:GET /api/stream/aiko-taylor-neon-skyline/direct 实测回显:
{"statusCode":400,"message":"Identifier: expected a number, got aiko-taylor-upload-probe"}
  • 定位:/api/songs、/api/albums 等端点同时接受数字 ID 与 slug,但 /api/stream 的路径参数是 File 表主键,只接受数字。这是 API 设计的不一致点(swagger 中参数名叫 idOrSlug 但实测 stream 域只认数字)。
  • 解决:先查 GET /api/songs/{slug} 拿到数字 id/masterId,用它拼 stream URL。D5 篇各域实测均标注了 ID 类型。

问题 5:HLS 播放列表 400,提示 X-CLIENT-ID 或 Invalid quality

  • 现象:GET /api/stream/2/master.m3u8 实测回显 {"errors":["missing client id. Please specify the X-CLIENT-ID header ..."]};把音质段写成 audio/0/low/index.m3u8 实测回显 {"errors":["Invalid quality"]}。
  • 定位:转码器按"播放器会话"缓存分片,要求每个播放器实例带全局唯一的 X-CLIENT-ID;音质档位名不是 low/medium/high,实测合法值为 original 与 128k(由 master.m3u8 返回的 #EXT-X-MEDIA 行给出)。
  • 解决:请求任何 m3u8 前固定一个 UUID 形式的 X-CLIENT-ID 头;音质档位照抄 master.m3u8 返回值。前端播放器会自动处理这两件事,手动调 API 时容易踩。

问题 6:注册按钮置灰,无法创建第一个账号

  • 现象:注册页按钮不可点击。
  • 定位(官方 Wiki Troubleshooting 页确认):两种原因——ENABLE_USER_REGISTRATION=0 关闭了注册;或前端无法连通 API(PUBLIC_URL 配错端口/协议,最常见是把内网地址配成了公网域名)。
  • 解决:确认 .env 的 PUBLIC_URL=http://192.168.146.128:8140 与浏览器地址栏一致后 docker compose up -d 重建 front;首账号建好后建议 ENABLE_USER_REGISTRATION=0 收紧注册。

十二、参考链接