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 失败三则排障实录。

LibreTimeLibreTime2026/10/29

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-nginxWeb 统一入口(静态资源+反代)4.5.0
ghcr.io/libretime/libretime-legacy传统 Web 界面 + 上传接口(PHP)4.5.0
ghcr.io/libretime/libretime-apiREST 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.ymlbind 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_VERSIONlatestLibreTime 镜像版本号,建议固定小数版本如 4.5.0
POSTGRES_USERlibretime数据库超级用户名
POSTGRES_PASSWORDlibretime数据库口令,仅首次初始化生效
RABBITMQ_DEFAULT_VHOST/libretime消息队列虚拟主机
RABBITMQ_DEFAULT_USERlibretime消息队列用户名
RABBITMQ_DEFAULT_PASSlibretime消息队列口令
ICECAST_SOURCE_PASSWORDhackmeliquidsoap 向 icecast 推流的源口令
ICECAST_ADMIN_PASSWORDhackmeicecast 管理接口口令(stats.xml 等)
ICECAST_RELAY_PASSWORDhackmeicecast 中继口令
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.timezoneUTC排期显示时区,中国部署建议 Asia/Shanghai
general.cache_ahead_hours1playout 提前预取排期的时长(小时)
general.authlocallegacy 认证适配器,可接 FreeIpa
storage.path/srv/libretime媒体库根路径(容器内)
database.host/user/passwordlocalhost/libretime/libretime数据库连接
rabbitmq.host/user/passwordlocalhost/libretime/libretime消息队列连接
playout.liquidsoap_host/portlocalhost/1234playout 连 liquidsoap server 协议
liquidsoap.server_listen_port1234liquidsoap 控制端口(容器内)
stream.inputs.main/showmount=main/show, port=8001/8002两个直播输入口
stream.outputs.icecastenabled=false流输出(最多 3 路),含格式/码率/挂载点
stream.outputs.shoutcastenabled=falseShoutcast 输出(最多 1 路)

改配置后生效方式:docker compose -p libretime-450-stack restart <受影响组件>(配置为只读挂载,重启重读)。

八、配置文件挂载

挂载关系一览(实测 docker inspect 核对):

宿主路径组件内路径读写
/opt/libretime/config.yml/etc/libretime/config.yml:ro(全部 7 个 LibreTime 组件)
命名卷 libretime_storage/srv/libretimelegacy/analyzer/api 读写、nginx 只读
命名卷 libretime_playout/appplayout/liquidsoap 共享(预缓存)
命名卷 postgres_data/var/lib/postgresql/datapostgres

核对挂载的实测命令:

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。

十二、参考链接