LibreTime 4.5.0 Docker Compose 部署指南
LibreTime 4.5.0 Docker Compose 从零部署实录:官方 10 组件编排(nginx/legacy/api/playout/liquidsoap/analyzer/worker/icecast/postgres/rabbitmq)、.env 与 config.yml 最小必配、数据库 migrate 初始化、管理员改密、上传排期与 Ogg 流输出验证,含 cc_subjs 缺表、playout 连环退出、changepassword 失败三则排障实录。
LibreTime 4.5.0 Docker Compose 部署指南
目标:在一台 Linux 服务器上用 Docker Compose 从零部署 LibreTime 4.5.0 电台自动化平台全栈(10 个组件),完成数据库初始化、管理员改密、媒体上传、节目排期与流输出验证,交付一套可播出的网络电台。已在 Ubuntu 22.04 x86_64(Docker 28 + Compose v2)实测通过全流程,含三处真实排障实录。
一、环境信息
本文所有命令与回显均在以下环境实测:
- 操作系统:Ubuntu 22.04 x86_64(4C/8G,内核 5.15)
- Docker:Docker Engine 28.x + Docker Compose v2(
docker compose子命令形态) - LibreTime:4.5.0(官方镜像
ghcr.io/libretime/libretime-*:4.5.0,Icecast 2.4.4) - 组件清单(10 个):nginx / legacy / api / playout / liquidsoap / analyzer / worker / icecast / postgres 15 / rabbitmq 3.13-alpine
- 端口规划:8142=Web 界面与 API(→nginx 8080),8138=Icecast 流(→8000),8139=主直播输入(→8001),8140=秀直播输入(→8002)
- 部署目录:
/opt/libretime/(compose 项目目录,读者可自定) - 网络:专用 bridge 网络(本文命名
libretime-4.5.0-net,网段 10.89.58.0/24) - 演示账号:Web 管理员
admin/q1w2e3r4(初始admin/admin,部署中改密)
二、镜像选型说明
LibreTime 官方按组件拆分镜像,全部发布在 GitHub Container Registry(ghcr.io),用统一 tag 表达版本:
| 镜像 | 用途 | 本文采用 tag |
|---|---|---|
| ghcr.io/libretime/libretime-nginx | Web 统一入口(静态资源+反代) | 4.5.0 |
| ghcr.io/libretime/libretime-legacy | 传统 Web 界面 + 上传接口(PHP) | 4.5.0 |
| ghcr.io/libretime/libretime-api | REST API v2 + 数据库迁移(Python/Django) | 4.5.0 |
| ghcr.io/libretime/libretime-playout | 排播调度器(Python) | 4.5.0 |
| ghcr.io/libretime/libretime-analyzer | 媒体元数据分析(Python) | 4.5.0 |
| ghcr.io/libretime/libretime-worker | 异步任务执行(Python) | 4.5.0 |
| ghcr.io/libretime/icecast | 流媒体分发服务器 | 2.4.4 |
| postgres | 业务数据库 | 15 |
| rabbitmq | 组件间消息队列 | 3.13-alpine |
选型要点:
- liquidsoap 复用 playout 镜像:
libretime-playout镜像内同时包含 playout 与libretime-liquidsoap两个可执行入口(官方 compose 即以command: /usr/local/bin/libretime-liquidsoap起第二个服务),无需单独的 liquidsoap 镜像。 - tag 用小数版本不用 latest:
.env里LIBRETIME_VERSION="4.5.0"固定版本,升级改一行即可;latest会引入不可复现的升级风险。 - postgres 固定 15:LibreTime 4.5.0 官方编排采用 PostgreSQL 15,升大版本需迁移数据,无必要不追新。
- 镜像拉取:ghcr.io 国内直连速度不稳定,Docker 守护进程配置代理或使用镜像加速均可;本文拉取 9 个镜像共约 1.2GB,一次拉成。
三、前置条件
- 服务器已安装 Docker Engine 与 Compose v2(
docker compose version能输出版本号)。 - 端口 8138/8139/8140/8142 未被占用。
- 磁盘余量 ≥ 10GB(镜像约 1.2GB + 媒体库与数据库增长空间)。
- 服务器与监众之间网络可达(如对外提供服务,防火墙放行 8138/8142)。
- 生成两组 32 字节随机密钥(用于 API 内部认证与 Django 会话签名):
openssl rand -hex 32 # 第一组 → api_key
openssl rand -hex 32 # 第二组 → secret_key
实测回显(示例值,读者部署时应重新生成):
867b550987df8c6ff65140584d9cd6e93a18e386e8ed69b5e4c00dc858e01b73
94c3f2ca50b0465155b0c689ce24a0b4e4ec16cffbe0f2bc87e06842a3059a80
四、部署步骤
4.1 创建部署目录
mkdir -p /opt/libretime && cd /opt/libretime # 部署目录,配置与编排文件都放这里
4.2 编写 docker-compose.yml
以下为本文实测采用的编排文件(基于官方 4.5.0 compose,端口按本文规划映射):
services:
postgres:
image: postgres:15
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_USER: ${POSTGRES_USER:-libretime}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-libretime}
healthcheck:
test: pg_isready -U libretime
rabbitmq:
image: rabbitmq:3.13-alpine
environment:
RABBITMQ_DEFAULT_VHOST: ${RABBITMQ_DEFAULT_VHOST:-/libretime}
RABBITMQ_DEFAULT_USER: ${RABBITMQ_DEFAULT_USER:-libretime}
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS:-libretime}
healthcheck:
test: nc -z 127.0.0.1 5672
playout:
image: ghcr.io/libretime/libretime-playout:${LIBRETIME_VERSION:-latest}
init: true
ulimits:
nofile: 1024
depends_on:
- rabbitmq
volumes:
- ${LIBRETIME_CONFIG_FILEPATH:-./config.yml}:/etc/libretime/config.yml:ro
- libretime_playout:/app
environment:
LIBRETIME_GENERAL_PUBLIC_URL: http://nginx:8080
liquidsoap:
image: ghcr.io/libretime/libretime-playout:${LIBRETIME_VERSION:-latest}
command: /usr/local/bin/libretime-liquidsoap
init: true
ulimits:
nofile: 1024
ports:
- 8139:8001
- 8140:8002
depends_on:
- rabbitmq
volumes:
- ${LIBRETIME_CONFIG_FILEPATH:-./config.yml}:/etc/libretime/config.yml:ro
- libretime_playout:/app
environment:
LIBRETIME_GENERAL_PUBLIC_URL: http://nginx:8080
analyzer:
image: ghcr.io/libretime/libretime-analyzer:${LIBRETIME_VERSION:-latest}
init: true
ulimits:
nofile: 1024
depends_on:
- rabbitmq
volumes:
- ${LIBRETIME_CONFIG_FILEPATH:-./config.yml}:/etc/libretime/config.yml:ro
- libretime_storage:/srv/libretime
environment:
LIBRETIME_GENERAL_PUBLIC_URL: http://nginx:8080
worker:
image: ghcr.io/libretime/libretime-worker:${LIBRETIME_VERSION:-latest}
init: true
ulimits:
nofile: 1024
depends_on:
- rabbitmq
volumes:
- ${LIBRETIME_CONFIG_FILEPATH:-./config.yml}:/etc/libretime/config.yml:ro
environment:
LIBRETIME_GENERAL_PUBLIC_URL: http://nginx:8080
api:
image: ghcr.io/libretime/libretime-api:${LIBRETIME_VERSION:-latest}
init: true
ulimits:
nofile: 1024
depends_on:
- postgres
- rabbitmq
volumes:
- ${LIBRETIME_CONFIG_FILEPATH:-./config.yml}:/etc/libretime/config.yml:ro
- libretime_storage:/srv/libretime
legacy:
image: ghcr.io/libretime/libretime-legacy:${LIBRETIME_VERSION:-latest}
init: true
ulimits:
nofile: 1024
depends_on:
- postgres
- rabbitmq
volumes:
- ${LIBRETIME_CONFIG_FILEPATH:-./config.yml}:/etc/libretime/config.yml:ro
- libretime_storage:/srv/libretime
nginx:
image: ghcr.io/libretime/libretime-nginx:${LIBRETIME_VERSION:-latest}
ports:
- 8142:8080
depends_on:
- legacy
volumes:
- libretime_storage:/srv/libretime:ro
icecast:
image: ghcr.io/libretime/icecast:2.4.4
ports:
- 8138:8000
environment:
ICECAST_SOURCE_PASSWORD: ${ICECAST_SOURCE_PASSWORD:-hackme}
ICECAST_ADMIN_PASSWORD: ${ICECAST_ADMIN_PASSWORD:-hackme}
ICECAST_RELAY_PASSWORD: ${ICECAST_RELAY_PASSWORD:-hackme}
volumes:
postgres_data: {}
libretime_storage: {}
libretime_playout: {}
逐项说明:
- 依赖链:playout/liquidsoap/analyzer/worker 依赖 rabbitmq;api/legacy 依赖 postgres 与 rabbitmq;nginx 依赖 legacy。注意
depends_on只保证启动顺序,不保证服务就绪——这正是本文 11.2 排障实录的根源。 - 配置文件只读挂载:所有 LibreTime 组件以
:ro挂同一个/etc/libretime/config.yml,改配置后需重启相关组件生效。 - 共享存储卷:
libretime_storage(媒体库文件)在 legacy/analyzer/api 间共享读写、nginx 只读;libretime_playout是 playout 的预缓存卷。 - 资源限制:
ulimits.nofile: 1024为官方默认句柄上限,媒体库特别大时可调高。
再准备一个 override 文件固定网络(可选,便于网络规划与复用):
# docker-compose.override.yml
name: libretime-4.5.0-stack
networks:
default:
name: libretime-4.5.0-net
driver: bridge
ipam:
config:
- subnet: 10.89.58.0/24
4.3 编写 .env(口令注入)
数据库、消息队列、Icecast 的口令统一放 .env,由 compose 注入(compose 会自动读取同目录 .env):
# /opt/libretime/.env
LIBRETIME_VERSION="4.5.0"
# Postgres
POSTGRES_PASSWORD=q1w2e3r4
# RabbitMQ
RABBITMQ_DEFAULT_PASS=q1w2e3r4
# Icecast
ICECAST_SOURCE_PASSWORD=q1w2e3r4
ICECAST_ADMIN_PASSWORD=q1w2e3r4
ICECAST_RELAY_PASSWORD=q1w2e3r4
说明:以上口令为演示值,生产部署请换成长随机串并收紧 .env 文件权限(chmod 600 .env)。POSTGRES_PASSWORD 仅在 postgres 数据卷首次初始化时生效,改口令需进库执行 ALTER USER 或删卷重建。
4.4 编写 config.yml(最小必配)
LibreTime 全组件共享一份 /etc/libretime/config.yml,最小必填三件事:general.public_url、general.api_key、general.secret_key,以及组件互连的数据库/消息队列/流输出口令。实测采用的配置(完整参数逐项解释见《LibreTime 4.5.0 配置参数详解》):
general:
# 对外访问地址:听众与 DJ 浏览器实际访问的 URL,换成你的服务器地址
public_url: http://127.0.0.1:8142
# 内部 API 认证密钥(4.2 节 openssl 生成)
api_key: 867b550987df8c6ff65140584d9cd6e93a18e386e8ed69b5e4c00dc858e01b73
# Django 会话签名密钥(4.2 节 openssl 生成)
secret_key: 94c3f2ca50b0465155b0c689ce24a0b4e4ec16cffbe0f2bc87e06842a3059a80
timezone: Asia/Shanghai
storage:
path: /srv/libretime
database:
host: postgres
port: 5432
name: libretime
user: libretime
password: q1w2e3r4
rabbitmq:
host: rabbitmq
port: 5672
vhost: /libretime
user: libretime
password: q1w2e3r4
liquidsoap:
server_listen_address: 0.0.0.0
server_listen_port: 1234
stream:
inputs:
main:
mount: main
port: 8001
secure: false
show:
mount: show
port: 8002
secure: false
outputs:
.default_icecast_output: &default_icecast_output
host: icecast
port: 8000
source_password: q1w2e3r4
admin_password: q1w2e3r4
name: LibreTime!
description: LibreTime Radio!
website: https://libretime.org
genre: various
icecast:
- <<: *default_icecast_output
enabled: true
mount: main
audio:
format: ogg
bitrate: 256
逐项说明:
public_url是唯一需要写「对外地址」的地方,影响直播输入口的公开 URL 生成与界面展示;本文以本机回环演示,生产换成http://<服务器IP或域名>:8142。api_key同时是 legacy/rest/*接口的 Basic 认证用户名(见 4.7 节上传实测),务必与 secret_key 一样保密。database/rabbitmq的 host 写 compose 服务名(postgres/rabbitmq),专用网络内按名称解析。stream.outputs.icecast用 YAML 锚点&default_icecast_output复用公共输出参数,第一个输出enabled: true、挂载点main、Ogg 256kbps。source_password/admin_password必须与.env的ICECAST_SOURCE_PASSWORD/ICECAST_ADMIN_PASSWORD一致,否则 liquidsoap 无法向 icecast 推流。
4.5 拉起全栈
docker compose -p libretime-450-stack up -d
实测回显(节选):
Container libretime-450-stack-postgres-1 Starting
Container libretime-450-stack-postgres-1 Started
Container libretime-450-stack-rabbitmq-1 Started
Container libretime-450-stack-api-1 Started
Container libretime-450-stack-legacy-1 Started
Container libretime-450-stack-nginx-1 Started
-p libretime-450-stack 显式固定项目名,容器名形如 libretime-450-stack-<服务>-1,便于后续按名操作。核对全部组件运行状态:
docker ps --filter 'name=libretime-450-stack' --format '{{.Names}}\t{{.Status}}' | sort
实测回显(首次拉起后):
libretime-450-stack-analyzer-1 Up 42 seconds
libretime-450-stack-api-1 Up 41 seconds (healthy)
libretime-450-stack-icecast-1 Up 42 seconds
libretime-450-stack-legacy-1 Up 41 seconds
libretime-450-stack-liquidsoap-1 Up 40 seconds
libretime-450-stack-nginx-1 Up 40 seconds
libretime-450-stack-playout-1 Up 41 seconds
libretime-450-stack-postgres-1 Up 42 seconds (healthy)
libretime-450-stack-rabbitmq-1 Up 41 seconds (healthy)
libretime-450-stack-worker-1 Up 41 seconds
⚠️ 首次拉起后 liquidsoap 与 playout 可能因「API 未初始化」退出(
Exited (1)),属已知启动顺序问题,先完成 4.6 节数据库迁移,再docker compose -p libretime-450-stack restart liquidsoap playout即可,实录见 11.2。
4.6 初始化数据库(首次必做)
4.5.0 的编排不会自动建表,首次启动必须手动执行一次迁移:
docker exec libretime-450-stack-api-1 libretime-api migrate --noinput
实测回显(节选,共 46+ 个迁移全部 OK):
Applying legacy.0036_3_0_0_alpha_13_10... OK
Applying legacy.0041_drop_stream_setting_table... OK
Applying legacy.0044_add_track_types_analyzer_options... OK
Applying legacy.0045_add_sessions_table... OK
Applying legacy.0046_add_override_intro_outro_playlists... OK
Applying sessions.0001_initial... OK
验证表已创建(应看到 41 张表):
docker exec libretime-450-stack-postgres-1 psql -U libretime -c '\dt' | tail -5
实测回显:
public | imported_podcast | table | libretime
public | podcast | table | libretime
public | podcast_episodes | table | libretime
public | sessions | table | libretime
public | station_podcast | table | libretime
public | third_party_track_references | table | libretime
(41 rows)
迁移同时创建了内置管理员(实测查库确认):
docker exec libretime-450-stack-postgres-1 psql -U libretime -c 'SELECT id, login, type FROM cc_subjs;'
实测回显:
id | login | type
----+-------+------
1 | admin | A
(1 row)
迁移完成后重启受启动顺序影响的组件:
docker compose -p libretime-450-stack restart liquidsoap playout
4.7 修改管理员密码
初始账号为 admin/admin,生产必须改密。实测 changepassword 管理命令对交互终端有依赖且密码策略会拒绝简单口令(实录见 11.3),推荐用 shell 方式直接改:
docker exec libretime-450-stack-api-1 libretime-api shell -c "
from django.apps import apps
U = apps.get_model('core', 'User')
u = U.objects.get(username='admin')
u.set_password('q1w2e3r4')
u.save()
print('updated:', u.username)
"
实测回显:
updated: admin
验证 API 认证已生效(HTTP 200 为改密成功):
curl -s -u 'admin:q1w2e3r4' 'http://127.0.0.1:8142/api/v2/users' -o /dev/null -w '%{http_code}\n'
实测回显:
200
浏览器打开 http://<服务器IP>:8142/login,用 admin / q1w2e3r4 登录进入工作台(实测登录返回 200,页面标题 LibreTime)。
4.8 上传媒体并验证自动播出
上传一首测试音频(legacy 上传接口用 HTTP Basic 认证,用户名=config.yml 的 api_key、密码留空,即 -u '<API_KEY>:' 形态;下例直接使用 4.4 节生成的实际密钥值):
curl -s -u '867b550987df8c6ff65140584d9cd6e93a18e386e8ed69b5e4c00dc858e01b73:' \
-X POST 'http://127.0.0.1:8142/rest/media' \
-F 'file=@test-track.mp3;type=audio/mpeg' -o /tmp/up.json -w '%{http_code}\n'
实测回显:
201
约 15 秒后分析完成,确认元数据(时长 3 分钟、码率 192kbps):
curl -s -u 'admin:q1w2e3r4' 'http://127.0.0.1:8142/api/v2/files/1' \
| python3 -c "import sys,json; d=json.load(sys.stdin); print('import_status:', d['import_status'], '| length:', d['length'], '| bit_rate:', d['bit_rate'])"
实测回显:
import_status: 0 | length: 00:03:00.035918 | bit_rate: 192000
建一条 3 分钟后的排期(show → instance → schedule,REST 接口逐条实测):
curl -s -u 'admin:q1w2e3r4' -X POST 'http://127.0.0.1:8142/api/v2/shows' \
-H 'Content-Type: application/json' \
-d '{"name":"Test Show","description":"forensic test show","genre":"various","linked":false,"linkable":false,"auto_playlist_enabled":false,"auto_playlist_repeat":false,"override_intro_playlist":false,"override_outro_playlist":false,"live_enabled":false}'
实测回显(节选):
show id: 1 | name: Test Show
curl -s -u 'admin:q1w2e3r4' -X POST 'http://127.0.0.1:8142/api/v2/show-instances' \
-H 'Content-Type: application/json' \
-d '{"show":1,"starts_at":"2026-10-29T10:26:00Z","ends_at":"2026-10-29T10:46:00Z","record_enabled":0,"modified":true,"created_at":"2026-10-29T10:24:00Z","auto_playlist_built":false}'
实测回显:
instance id: 1 | starts: 2026-10-29T10:26:00Z
curl -s -u 'admin:q1w2e3r4' -X POST 'http://127.0.0.1:8142/api/v2/schedule' \
-H 'Content-Type: application/json' \
-d '{"instance":1,"file":1,"starts_at":"2026-10-29T10:26:00Z","ends_at":"2026-10-29T10:29:00Z","length":"00:03:00.035918","position":0,"fade_in":"00:00:00","fade_out":"00:00:00","cue_in":"00:00:00","cue_out":"00:03:00.035918","broadcasted":0,"played":false}'
实测回显:
schedule id: 1 | starts: 2026-10-29T10:26:00Z
到点后验证播出与流输出。playout 侧日志(实测):
2026-10-29 10:26:56,890 | INFO | libretime_playout.player.file:copy_file:47 - copying file 1 to cache /app/scheduler/1.mp3
2026-10-29 10:26:57,003 | INFO | libretime_playout.player.queue:main:57 - New schedule received
liquidsoap 侧日志(实测):
2026/10/29 10:26:57 [s3:3] Prepared "/app/scheduler/1.mp3" (RID 0).
2026/10/29 10:26:57 [cue_cut_9616:3] Cueing in...
2026/10/29 10:26:57 [switch:blank+schedule:3] Switch to map_metadata:schedule with transition.
从流地址拉 4 秒采样验证输出:
curl -s -o /tmp/s.bin --max-time 4 'http://127.0.0.1:8138/main' \
-w 'stream: HTTP %{http_code}, %{size_download}B, %{content_type}\n'
file /tmp/s.bin
实测回显:
stream: HTTP 200, 92686B, application/ogg
/tmp/s.bin: Ogg data, Vorbis audio, stereo, 44100 Hz, ~256000 bps, created by: Xiph.Org libVorbis I
播出记录回写验证:
curl -s -u 'admin:q1w2e3r4' 'http://127.0.0.1:8142/api/v2/playout-history?starts_at=2026-10-29T00:00:00Z&ends_at=2026-10-30T00:00:00Z'
实测回显:
[{"id":1,"starts":"2026-10-29T10:26:00Z","ends":"2026-10-29T10:29:00Z","file":1,"instance":1}]
至此部署闭环:Web 可登录、上传分析正常、排期到点自动播出、听众可从 http://<服务器IP>:8138/main 收听。
五、数据持久化说明
全栈共 3 个命名数据卷 + 1 个 bind mount,用途与备份建议:
| 存储 | 类型 | 内容 | 备份优先级 |
|---|---|---|---|
| postgres_data | 命名卷 | 全部业务数据(媒体库索引/节目/排期/用户/历史) | 最高,每日备份 |
| libretime_storage | 命名卷 | 上传的音频文件(/srv/libretime/imported/...) | 最高,与库同频 |
| libretime_playout | 命名卷 | playout 预缓存(可随时重建) | 无需备份 |
| ./config.yml | bind mount | 全局配置 | 纳入版本管理 |
备份实操(数据库逻辑备份 + 媒体文件打包):
docker exec libretime-450-stack-postgres-1 pg_dump -U libretime libretime > libretime_db_$(date +%F).sql
docker run --rm -v libretime-450-stack_libretime_storage:/data -v $PWD:/backup ubuntu tar czf /backup/storage_$(date +%F).tgz -C /data .
💡 说明:
docker run --rm ... tar一次性起一个临时挂载会话把卷内容打包到当前目录,是最通用的卷备份手法;恢复时反向解包即可。
验证备份产物:
ls -lh libretime_db_*.sql storage_*.tgz
实测回显(示例,库中已有 1 首媒体与排期记录时):
-rw-r--r-- 1 root root 99K Oct 29 18:33 libretime_db_2026-10-29.sql
-rw-r--r-- 1 root root 2.7M Oct 29 18:33 storage_2026-10-29.tgz
六、环境变量表
.env 中全部变量(compose 注入侧,均为部署期固定值):
| 变量 | 默认值 | 说明 |
|---|---|---|
| LIBRETIME_VERSION | latest | LibreTime 镜像版本号,建议固定小数版本如 4.5.0 |
| POSTGRES_USER | libretime | 数据库超级用户名 |
| POSTGRES_PASSWORD | libretime | 数据库口令,仅首次初始化生效 |
| RABBITMQ_DEFAULT_VHOST | /libretime | 消息队列虚拟主机 |
| RABBITMQ_DEFAULT_USER | libretime | 消息队列用户名 |
| RABBITMQ_DEFAULT_PASS | libretime | 消息队列口令 |
| ICECAST_SOURCE_PASSWORD | hackme | liquidsoap 向 icecast 推流的源口令 |
| ICECAST_ADMIN_PASSWORD | hackme | icecast 管理接口口令(stats.xml 等) |
| ICECAST_RELAY_PASSWORD | hackme | icecast 中继口令 |
| LIBRETIME_CONFIG_FILEPATH | ./config.yml | 宿主侧配置文件路径(挂载进各组件) |
另有编译期环境变量 LIBRETIME_GENERAL_PUBLIC_URL(写在 compose 的 environment 里,值 http://nginx:8080):供组件在容器网络内部互相访问时使用,与 config.yml 的 general.public_url(对外地址)是两回事,不要混配。
七、配置参数说明
config.yml 是 LibreTime 的唯一全局配置,10 个组件以只读方式共享。4.5.0 的完整参数树与默认值可用官方模板核对:
ls /opt/libretime/ # 部署目录应包含官方附带的 config.template.yml
高频参数速览(完整逐项解释见《LibreTime 4.5.0 配置参数详解》):
| 参数 | 默认值 | 作用 |
|---|---|---|
| general.public_url | 无(必填) | 对外访问地址,影响直播输入 URL 与界面展示 |
| general.api_key | 无(必填) | 内部 API 认证 + legacy /rest/* 的 Basic 用户名 |
| general.secret_key | 无(必填) | Django 会话签名密钥 |
| general.timezone | UTC | 排期显示时区,中国部署建议 Asia/Shanghai |
| general.cache_ahead_hours | 1 | playout 提前预取排期的时长(小时) |
| general.auth | local | legacy 认证适配器,可接 FreeIpa |
| storage.path | /srv/libretime | 媒体库根路径(容器内) |
| database.host/user/password | localhost/libretime/libretime | 数据库连接 |
| rabbitmq.host/user/password | localhost/libretime/libretime | 消息队列连接 |
| playout.liquidsoap_host/port | localhost/1234 | playout 连 liquidsoap server 协议 |
| liquidsoap.server_listen_port | 1234 | liquidsoap 控制端口(容器内) |
| stream.inputs.main/show | mount=main/show, port=8001/8002 | 两个直播输入口 |
| stream.outputs.icecast | enabled=false | 流输出(最多 3 路),含格式/码率/挂载点 |
| stream.outputs.shoutcast | enabled=false | Shoutcast 输出(最多 1 路) |
改配置后生效方式:docker compose -p libretime-450-stack restart <受影响组件>(配置为只读挂载,重启重读)。
八、配置文件挂载
挂载关系一览(实测 docker inspect 核对):
| 宿主路径 | 组件内路径 | 读写 |
|---|---|---|
| /opt/libretime/config.yml | /etc/libretime/config.yml | :ro(全部 7 个 LibreTime 组件) |
| 命名卷 libretime_storage | /srv/libretime | legacy/analyzer/api 读写、nginx 只读 |
| 命名卷 libretime_playout | /app | playout/liquidsoap 共享(预缓存) |
| 命名卷 postgres_data | /var/lib/postgresql/data | postgres |
核对挂载的实测命令:
docker inspect libretime-450-stack-api-1 --format '{{range .Mounts}}{{.Source}} -> {{.Destination}} ({{.Mode}}){{println}}{{end}}'
实测回显(命名卷的宿主侧真实路径随 Docker 存储驱动展示为 volume 根路径):
/var/lib/docker/volumes/libretime-450-stack_libretime_storage/_data -> /srv/libretime (rw)
/opt/libretime/config.yml -> /etc/libretime/config.yml (ro)
九、等价 docker run
理解编排本质后,任一组件都可用 docker run 等价启动。以最核心的 api 为例(其余组件同理,仅镜像/端口/卷不同):
docker network create libretime-4.5.0-net
docker run -d --name libretime-api \
--network libretime-4.5.0-net \
-v /opt/libretime/config.yml:/etc/libretime/config.yml:ro \
-v libretime-450-stack_libretime_storage:/srv/libretime \
ghcr.io/libretime/libretime-api:4.5.0
说明:等价手工方式仅用于理解与应急调试(如临时起一个 analyzer 排查消费积压);生产仍用 compose 管理,避免 10 个组件的手工编排漂移。
十、常用运维命令
# 全栈状态(应 10 个全部 Up,postgres/rabbitmq/api 带 healthy)
docker ps --filter 'name=libretime-450-stack' --format '{{.Names}}\t{{.Status}}' | sort
# 单组件日志跟踪(排障第一步)
docker logs -f libretime-450-stack-playout-1
# 重启单个组件(改 config.yml 后)
docker compose -p libretime-450-stack restart liquidsoap
# 全栈停止(保留数据)
docker compose -p libretime-450-stack down
# 全栈停止并删除数据卷(彻底重置,媒体库与数据库清空)
docker compose -p libretime-450-stack down -v
# 升级:改 .env 的 LIBRETIME_VERSION 后
docker compose -p libretime-450-stack up -d
实测回显(down -v,本文重置实验时采集):
Volume libretime-450-stack_postgres_data Removed
Volume libretime-450-stack_libretime_storage Removed
Volume libretime-450-stack_libretime_playout Removed
升级后如上游包含 schema 变更,重跑一次迁移:
docker exec libretime-450-stack-api-1 libretime-api migrate --noinput
十一、常见问题(排障实录)
以下三个问题均为本文部署过程中真实发生、逐一定位并解决的实录。
11.1 Web 界面 500:relation "cc_subjs" does not exist
现象:首次拉起后访问 http://127.0.0.1:8142/login 报 500,legacy 日志出现:
2026-10-29T10:19:20+00:00 ERR (3): Zend_Db_Table_Exception: Primary key column(s) (id) are not columns in this table ()
postgres: ERROR: relation "cc_subjs" does not exist at character 314
定位:查 api 日志同样报 relation "cc_pref" does not exist,且 api 启动日志只有 gunicorn 启动记录、没有任何迁移输出——4.5.0 官方编排不自动执行数据库迁移。
解决:手动执行一次迁移(见 4.6 节),迁移完成后重启 legacy,登录页恢复 200。
结论:把「首次部署后先 migrate」纳入部署清单固定步骤;升级同理。
11.2 liquidsoap 与 playout 连环 Exited (1)
现象:首次 up -d 数分钟后核对状态,发现:
libretime-450-stack-liquidsoap-1 Exited (1) 7 minutes ago
libretime-450-stack-playout-1 Exited (1) 6 minutes ago
定位:liquidsoap 日志尾部:
requests.exceptions.RetryError: HTTPConnectionPool(host='nginx', port=8080): Max retries exceeded with url: /api/v2/info (Caused by ResponseError('too many 500 error responses'))
playout 日志尾部:
libretime_playout.liquidsoap.client._client.LiquidsoapClientError: could not get liquidsoap version
链路是:迁移未完成时 /api/v2/info 一律 500 → liquidsoap 重试耗尽退出 → playout 等不到 liquidsoap 版本应答退出。二者都是 11.1 的下游连锁故障。
解决:先完成 4.6 迁移,再重启两个组件:
docker compose -p libretime-450-stack restart liquidsoap playout
docker ps --filter 'name=libretime-450-stack' --format '{{.Names}}\t{{.Status}}' | grep -E 'liquidsoap|playout'
实测回显:
libretime-450-stack-liquidsoap-1 Up 37 seconds
libretime-450-stack-playout-1 Up 37 seconds
结论:重启顺序是「先迁移、再 liquidsoap、后 playout」(playout 启动时要探测 liquidsoap 版本)。排查连环退出时先看上游 api 是否 500。
11.3 管理命令改密失败:This password is too common
现象:用 libretime-api changepassword admin 改密(管道喂入两次口令),报:
This password is too common.
EOFError / termios.error: (25, 'Inappropriate ioctl for device')
定位:Django 密码校验器拒绝 q1w2e3r4 这类常见口令,且非交互终端下 getpass 读不到输入直接 EOF;即使口令合规,该命令也强依赖 TTY。
解决:走 shell -c 用模型方法 set_password 直接写(绕过表单校验器,见 4.7 节),实测改密后 API Basic 认证返回 200。
结论:容器内非交互场景改密用 libretime-api shell -c;顺带发现 legacy 侧密码以 MD5 摘要形式存储(数据库 cc_subjs.pass 为 32 位十六进制串),安全要求高的部署应限制管理面暴露。
11.4 上传接口 401/400 速查
- 用
X-API-Key头调 legacy/rest/media会报ERROR: Incorrect API key.——legacy REST 接口认的是 Basic 认证(用户名=api_key,密码留空),即curl -u '<API_KEY>:';X-API-Key头只对 Django 侧部分端点(如/api/v2/version)生效。 POST /rest/media/upload返回 400 且日志报invalid input syntax for type integer: "upload"——正确路径是POST /rest/media(RESTful 风格,id 由服务端生成),upload被当成了媒体 id 去查库。POST /api/v2/files是元数据接口(要求 size/mime 等字段),不接受文件本体重传;文件上传固定走 legacy/rest/media。
十二、参考链接
- 官方 Docker 部署文档:https://libretime.org/docs/admin-manual/setup/docker/
- 官方 compose 编排源文件:https://github.com/libretime/libretime/blob/main/docker-compose.yml
- 配置参考:https://libretime.org/docs/admin-manual/setup/configuration/
- Icecast 镜像:https://github.com/libretime/libretime/pkgs/container/libretime-icecast
- 《LibreTime 4.5.0 介绍与选型指南》 《LibreTime 4.5.0 配置参数详解》 《LibreTime 4.5.0 REST API 使用详解》