Solara 2.1.6 Docker Compose 部署指南

用 Docker Compose 在 Ubuntu 22.04 单机部署 Solara 2.1.6 自托管网页音乐播放器:编排文件编写、口令认证、SQLite 数据持久化、搜索/歌词/封面/播放直链与 Range 流全链路实测,含 compose 项目名规范化与上游 API 波动等真实排障结论。

SolaraSolara音乐流媒体自托管2026/10/25

Solara 2.1.6 Docker Compose 部署指南

目标:用 Docker Compose 在单台 Ubuntu 22.04 服务器上从零部署 Solara 2.1.6「光域」网页音乐播放器,完成「编排启动 → 口令认证 → 搜索/歌词/封面/播放直链 → 音频流 Range 播放 → 数据持久化」的完整验证链路。本文全部命令与回显已在 Docker Engine 29.9.2(Compose v5.6.1)环境实测通过。

一、环境信息

  • 操作系统:Ubuntu 22.04.5 LTS(x86_64)
  • Docker Engine:29.9.2,Docker Compose 插件 v5.6.1
  • Solara 版本:2.1.6(2026-09-11 发布,官方最新稳定 tag,版本信息查询日期 2026-10-25)
  • 官方镜像:ghcr.io/akudamatata/solara:2.1.6(linux/amd64,765020763 字节 ≈ 765 MB,声明暴露 8787/tcp)
  • 端口规划:宿主 8136 → 容器 8787(容器内端口由入口脚本固定为 8787,改宿主侧映射即可)
  • 编排目录:/opt/solara/(示例路径,含 docker-compose.yml 与 data/)
  • 独立网络:solara-net(bridge,网段 10.89.65.0/24,容器固定 IP 10.89.65.10)
  • 上游音乐 API:https://music-api.gdstudio.xyz/api.php(GD音乐台,项目默认聚合源)
  • 演示访问口令:q1w2e3r4
  • 实测资源基线:空载 CPU 0.88%、内存 195.4 MiB、进程数 71

二、镜像选型说明

Solara 官方只发布单一镜像,没有 slim/alpine 等变体,选型集中在仓库来源与标签策略两点:

项目取值说明
官方镜像仓库ghcr.io/akudamatata/solaraGitHub Container Registry,与源码仓库同属一个组织,唯一官方发布渠道
精确版本标签2.1.6与仓库 tag v2.1.6 对应,生产推荐,升级可控、回显可复现
滚动最新标签latest跟随最新稳定版,docker compose pull 即升级,但版本不可复现
第三方重打包镜像aexus/solara-music不要使用:仅 latest 一个标签、体积 51.9 MB(官方 765 MB)、2025-11-16 后停更,与官方镜像差异巨大且无人维护

镜像关键元数据(docker image inspect 实测):

docker image inspect ghcr.io/akudamatata/solara:2.1.6 \
  --format 'Size={{.Size}} {{.Os}}/{{.Architecture}} Exposed={{json .Config.ExposedPorts}} Entrypoint={{json .Config.Entrypoint}}'

实测输出:

Size=765020763 linux/amd64 Exposed={"8787/tcp":{}} Entrypoint=["/app/docker-entrypoint.sh"]
  • 镜像体积约 765 MB,主要来自 Node.js 运行时 + Wrangler(Cloudflare Pages 本地开发服务器)依赖,比轻量静态站镜像大得多
  • 入口固定为 /app/docker-entrypoint.sh:先写 /app/.dev.vars,再拉起后台 Wrangler(127.0.0.1:8788)与前台 Express(0.0.0.0:8787)
  • 只暴露 8787 一个端口,8788 仅在容器内部回环监听,不对外映射

💡 说明:选精确标签 2.1.6 而非 latest,保证本文全部回显可复现;latest 适合个人尝鲜,但一旦上游推新版本,同样的编排文件行为可能变化。

三、前置条件

  1. 服务器已安装 Docker Engine 与 Compose 插件。验证:
docker version --format 'Engine {{.Server.Version}}'

实测输出:

Engine 29.9.2
docker compose version

实测输出:

Docker Compose version v5.6.1
  1. 端口未被占用(本文示例 8136):
ss -ltn | grep -q ':8141' && echo "8141 已被占用" || echo "8141 空闲"

实测输出:

8141 空闲

💡 说明:上面用 8141 做空闲探测示例,部署时把检查目标换成你实际要用的宿主端口即可。若已被占用,改编排文件的 ports 左侧值,或先停掉占用方。

  1. 宿主与上游 API 网络可达。Solara 自身不提供音乐数据,搜索/歌词/直链全部依赖上游聚合 API,部署前先确认出网正常:
curl -s -o /dev/null -w "HTTP %{http_code} time=%{time_total}s\n" --max-time 15 \
  "https://music-api.gdstudio.xyz/api.php?types=search&source=netease&name=test&count=1&pages=1"

实测输出:

HTTP 200 time=2.184193s
  1. 磁盘空间充足(镜像约 765 MB + 数据目录):
df -h /opt

实测输出:

Filesystem      Size  Used Avail Use% Mounted on
/dev/sda2       443G   72G  354G  17% /

四、部署步骤

本章按「建目录与编排 → 启动 → 状态确认 → 认证验证 → 上游接口验证 → 音频流验证 → 重启持久化」完成 0→1 部署。预期结果:服务健康运行、口令登录生效、搜索与播放链路全通、重启后数据不丢。

4.1 创建部署目录

mkdir -p /opt/solara && cd /opt/solara

实测输出:无输出(目录创建成功)。

💡 说明:编排目录内只需 docker-compose.yml 一个文件,运行时数据由 Compose 自动创建在 ./data/。

4.2 编写编排文件

/opt/solara/docker-compose.yml 完整内容:

name: solara-v216-stack

services:
  solara:
    image: ghcr.io/akudamatata/solara:2.1.6
    container_name: solara-v2.1.6-app
    restart: unless-stopped
    init: true
    ports:
      - "8136:8787"
    environment:
      - PASSWORD=q1w2e3r4
      - API_BASE_URL=https://music-api.gdstudio.xyz/api.php
      - language=ENG
    volumes:
      - ./data:/data
    networks:
      solara-net:
        ipv4_address: 10.89.65.10

networks:
  solara-net:
    driver: bridge
    ipam:
      config:
        - subnet: 10.89.65.0/24

逐项说明:

  • name:显式指定 Compose 项目名。⚠️ Docker 会把项目名规范化为「小写字母 + 数字 + 连字符」,点号会被剥除——若写成 solara-v2.1.6-stack,实际项目名会变成 solara-v216-stack。本文按规范化后的形态直接写 solara-v216-stack,避免后续 docker compose 命令与网络/容器命名对不上:
  • image:精确版本标签 2.1.6,与本文回显一致
  • container_name:固定实例名,便于 docker logs/docker exec 定位
  • restart: unless-stopped:宿主重启后自动拉起,手动 stop 后不会被自动拉起
  • init: true:官方建议项,用 tini 作为 1 号进程,解决 Node/Wrangler 双进程无法优雅响应 SIGTERM 导致的停止卡顿(否则 docker stop 要等超时才杀)
  • ports:宿主 8136 → 容器 8787
  • PASSWORD:访问口令,全站单一共享口令;留空即开放访问(任何人可直接进入播放器)
  • API_BASE_URL:上游聚合 API 地址,默认即 GD音乐台;上游被墙/被 Challenge 时换备用地址
  • language:界面语言,ENG 为英文(Docker 形态实测不生效,原因见第七章)
  • volumes:./data 挂到 /data,SQLite 数据库落在此处,是唯一需要持久化的目录
  • networks:自定义 bridge 网络并固定容器 IP,便于后续与反向代理/其他服务同栈编排

4.3 启动服务

docker compose up -d

实测输出:

 Network solara-v216-stack_solara-net Creating 
 Network solara-v216-stack_solara-net Creating 
 Network solara-v216-stack_solara-net Created 
 Network solara-v216-stack_solara-net Created 
 Container solara-v2.1.6-app Creating 
 Container solara-v2.1.6-app Created 
 Container solara-v2.1.6-app Starting 
 Container solara-v2.1.6-app Started 

💡 说明:首次启动会先 pull 约 765 MB 镜像,耗时取决于带宽;网络与容器各出现两行 Creating/Created 是 Compose 对项目级网络与容器描述符的正常输出。

4.4 状态与启动日志确认

docker compose ps

实测输出:

NAME                IMAGE                              COMMAND                  SERVICE   CREATED          STATUS          PORTS
solara-v2.1.6-app   ghcr.io/akudamatata/solara:2.1.6   "/app/docker-entrypo…"   solara    14 minutes ago   Up 14 minutes   0.0.0.0:8136->8787/tcp, [::]:8136->8787/tcp

STATUS 为 Up、PORTS 出现 0.0.0.0:8136->8787/tcp 即容器已正常监听。

docker logs solara-v2.1.6-app

实测输出:

Configuring environment variables for wrangler...
  [env] PASSWORD = configured
  [env] API_BASE_URL = configured
  [env] language = configured

  🌟 Solara  (Cloudflare Pages + Wrangler local dev)
  ────────────────────────────────────────────────────
  Port      : 8787
  Data dir  : /data
  Password  : configuredq1w2e3r4
  API URL   : https://music-api.gdstudio.xyz/api.php
  Language  : ENG
  ────────────────────────────────────────────────────

Starting wrangler pages dev in background on port 8788...
Starting Node.js standalone server on port 8787...
[DB] SQLite initialized at /data/solara.db
(node:7) ExperimentalWarning: SQLite is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)

  🌟 Solara Standalone Server
  ─────────────────────────────────────
  Ready on http://0.0.0.0:8787
  Password: ✓ configured
  ─────────────────────────────────────

关键三行解读:

  • [DB] SQLite initialized at /data/solara.db:服务端存储初始化成功,数据库落在挂载卷内
  • Ready on http://0.0.0.0:8787:Express 前台已监听
  • Starting wrangler pages dev in background on port 8788:Wrangler 代理进程已拉起(只在容器内回环监听)

⚠️ 安全警示(实测发现):启动横幅中的 Password : configuredq1w2e3r4 把访问口令明文打进了日志。原因是入口脚本的展开写法 ${PASSWORD:+configured}${PASSWORD:-not set (open access)} 在两个分支都非空时把 configured 与密码值直接拼接。生产环境务必限制 docker logs 的可见范围(见《Solara 日常运维手册》),或改用日志采集白名单过滤该行。

💡 说明:ExperimentalWarning: SQLite is an experimental feature 是 Node.js 内置 node:sqlite 模块的实验特性提示,不影响功能;Solara 用内置模块替代 Cloudflare D1,因此无需任何原生编译依赖。

4.5 访问控制验证

未登录访问首页,应被重定向到登录页:

curl -s -o /dev/null -w "GET / -> %{http_code} redirect=%{redirect_url}\n" http://127.0.0.1:8136/

实测输出:

GET / -> 302 redirect=http://127.0.0.1:8136/login

用错误口令登录,应返回 401:

curl -s -X POST http://127.0.0.1:8136/api/login \
  -H 'Content-Type: application/json' -d '{"password":"wrong-pass-2026"}' \
  -w "\nHTTP %{http_code}\n"

实测输出:

{"success":false}
HTTP 401

用正确口令(本文演示口令 q1w2e3r4)登录,应下发认证 Cookie:

curl -s -D - -o /dev/null -X POST http://127.0.0.1:8136/api/login \
  -H 'Content-Type: application/json' -d '{"password":"q1w2e3r4"}'

实测输出:

HTTP/1.1 200 OK
X-Powered-By: Express
Set-Cookie: auth=cTF3MmUzcjQ=; Max-Age=172800; Path=/; SameSite=Lax; HttpOnly
Content-Type: application/json; charset=utf-8
Content-Length: 16
ETag: W/"10-oV4hJxRVSENxc/wX8+mA4/Pe4tA"
Date: Sun, 25 Oct 2026 04:18:03 GMT
Connection: keep-alive
Keep-Alive: timeout=5

登录后带 Cookie 访问首页:

curl -s -c /opt/solara/cookies.txt -X POST http://127.0.0.1:8136/api/login \
  -H 'Content-Type: application/json' -d '{"password":"q1w2e3r4"}' > /dev/null
curl -s -b /opt/solara/cookies.txt -o /dev/null -w "GET / (auth cookie) -> %{http_code}\n" http://127.0.0.1:8136/

实测输出:

GET / (auth cookie) -> 200

💡 说明:auth=cTF3MmUzcjQ= 是口令的 Base64 编码(q1w2e3r4 → cTF3MmUzcjQ=),Cookie 有效期 Max-Age=172800(48 小时),带 HttpOnly 防脚本读取,SameSite=Lax。吊销口令即等于吊销所有会话——改 PASSWORD 后重建容器,旧 Cookie 立即失效。

4.6 上游接口链路验证

搜索歌曲(关键词「周杰伦」,源 netease):

curl -s -b /opt/solara/cookies.txt --max-time 60 \
  "http://127.0.0.1:8136/proxy?types=search&source=netease&name=%E5%91%A8%E6%9D%B0%E4%BC%A6&count=5&pages=1" \
  -o search.json -w "HTTP %{http_code} bytes=%{size_download} time=%{time_total}s\n"

实测输出:

HTTP 200 bytes=1188 time=6.454419s

解析返回的曲目(节选字段):

python3 -c "
import json
d=json.load(open('search.json'))
for s in d[:5]:
    print('-',s.get('name'),'/',','.join(s.get('artist',[])),'| id:',s.get('id'),'| source:',s.get('source'),'| pic_id:',s.get('pic_id'),'| lyric_id:',s.get('lyric_id'))
"

实测输出:

- 想你就写信 (Live) / 周杰伦,李硕,张鑫 | id: 509781655 | source: netease | pic_id: 109951163038292176 | lyric_id: 509781655
- 屋顶 / 周杰伦,温岚,吴宗宪 | id: 5257138 | source: netease | pic_id: 109951165671182684 | lyric_id: 5257138
- 布拉格广场 / 蔡依林,周杰伦 | id: 210049 | source: netease | pic_id: 109951172453712025 | lyric_id: 210049
- 默 (Live) / 李荣浩,周杰伦 | id: 1888354230 | source: netease | pic_id: 109951166537300832 | lyric_id: 1888354230
- 因为爱情 (Live) / 周杰伦,那英 | id: 490595315 | pic_id: 18892908300315027 | lyric_id: 490595315

取第一首的 ID 拉歌词:

curl -s -b /opt/solara/cookies.txt --max-time 60 \
  "http://127.0.0.1:8136/proxy?types=lyric&id=509781655&source=netease" \
  -o lyric.json -w "HTTP %{http_code} bytes=%{size_download} time=%{time_total}s\n"

实测输出:

HTTP 200 bytes=1411 time=1.380085s

歌词为标准的 LRC 文本({"lyric":"[00:00.00] 作词 : 方文山\n[00:01.00] 作曲 : 周杰伦\n[00:26.17]...),前端按时间戳逐行高亮。

取封面:

curl -s -b /opt/solara/cookies.txt --max-time 60 \
  "http://127.0.0.1:8136/proxy?types=pic&id=109951163038292176&source=netease&size=300" \
  -w "HTTP %{http_code} bytes=%{size_download} time=%{time_total}s\n"

实测输出:

HTTP 200 bytes=124 time=1.543759s
{"url":"https://p2.music.126.net/yD9vbpuILH-tqNRIaP640g==/109951163038292176.jpg?param=300y300","from":"music.gdstudio.xyz"}

取播放直链(128 kbps,强制跳过缓存以观察真实解析耗时):

curl -s -b /opt/solara/cookies.txt --max-time 120 \
  "http://127.0.0.1:8136/proxy?types=url&id=509781655&source=netease&br=128&nocache=true" \
  -w "HTTP %{http_code} bytes=%{size_download} time=%{time_total}s\n"

实测输出:

HTTP 200 bytes=234 time=2.900028s
{"url":"https://m801.music.126.net/20261025124313/c4588f6df5f923333c278975094650ea/jdymusic/obj/wo3DlMOGwrbDjj7DisKw/17432853135/a2d8/c265/26ad/fd9ff88a1226ec6b79a77ffca292d465.mp3","br":128,"size":3820077,"from":"music.gdstudio.xyz"}

⚠️ 音源说明(实测与 README 不符):README 宣称支持「网易云/QQ/酷狗」等数据源,实测 source=kuwo 时上游直接返回 HTTP 400:

{"detail":"Value of `source` is not supported."}
HTTP 400

当前实际可用的只有 netease。前端下拉仍保留 netease/kuwo/joox/bilibili 四项,选择非 netease 项会得到空结果或上述报错。

高码率风险:同一首歌 br=320/br=999(FLAC)实测解析 115 秒超时(HTTP 000),br=128 稳定在 3 秒内。生产建议默认音质用 128 或 192。

4.7 音频流播放验证

拿上一步返回的直链做 Range 探测(模拟播放器拖动进度条的分片请求):

curl -s -o /dev/null -H "Range: bytes=0-65535" \
  -w "HTTP %{http_code} type=%{content_type} bytes=%{size_download} time=%{time_total}s\n" \
  --max-time 60 "https://m801.music.126.net/20261025124313/c4588f6df5f923333c278975094650ea/jdymusic/obj/wo3DlMOGwrbDjj7DisKw/17432853135/a2d8/c265/26ad/fd9ff88a1226ec6b79a77ffca292d465.mp3"

实测输出:

HTTP 206 type=audio/mpeg bytes=65536 time=0.165539s

206 Partial Content + audio/mpeg 表示直链支持 Range 请求、可流式拖动播放;单次 64 KB 分片 0.17 秒返回,播放起播延迟很低。

4.8 重启与数据持久化验证

docker compose restart

实测输出:

 Container solara-v2.1.6-app Restarting 
 Container solara-v2.1.6-app Started 

重启后先写一条测试数据,再重启一次,验证数据落盘:

curl -s -b /opt/solara/cookies.txt -X POST http://127.0.0.1:8136/api/storage \
  -H 'Content-Type: application/json' \
  -d '{"data":{"doc-check-2026-10-25":"solara-2.1.6-ok"}}'

实测输出:

{"d1Available":true,"updated":1}

读回该键:

curl -s -b /opt/solara/cookies.txt \
  "http://127.0.0.1:8136/api/storage?keys=doc-check-2026-10-25"

实测输出:

{"d1Available":true,"data":{"doc-check-2026-10-25":"solara-2.1.6-ok"}}

再整体重启一次,读取全库确认既有数据仍在:

docker compose restart && sleep 4
curl -s -b /opt/solara/cookies.txt http://127.0.0.1:8136/api/storage

实测输出:

 Container solara-v2.1.6-app Restarting 
 Container solara-v2.1.6-app Started 
{"d1Available":true,"data":{"favorites:songs":"[{\"id\":\"5257138\",\"name\":\"屋顶\"}]","doc-check-2026-10-25":"solara-2.1.6-ok"}}

重启后数据完整保留。删除测试键:

curl -s -b /opt/solara/cookies.txt -X DELETE http://127.0.0.1:8136/api/storage \
  -H 'Content-Type: application/json' -d '{"keys":["doc-check-2026-10-25"]}'

实测输出:

{"d1Available":true,"deleted":1}

五、数据持久化说明

所有需要落盘的状态都在 /data(宿主 ./data),实测目录结构:

ls -la /opt/solara/data/

实测输出:

total 112
drwxr-xr-x 3 root root  4096 Oct 25 11:43 .
drwxr-xr-x 7 root root  4096 Oct 25 12:18 ..
-rw-r--r-- 1 root root  4096 Oct 25 11:43 solara.db
-rw-r--r-- 1 root root 32768 Oct 25 12:18 solara.db-shm
-rw-r--r-- 1 root root 61832 Oct 25 12:18 solara.db-wal
drwxr-xr-x 5 root root  4096 Oct 25 11:43 v3
  • solara.db:SQLite 主库,两张表 playback_store(播放状态/播放队列)与 favorites_store(收藏列表),跨设备漫游的收藏与进度都在这里
  • solara.db-wal / solara.db-shm:WAL 模式的日志与共享内存文件,备份时必须一起打包,只拷 solara.db 可能丢最近的写入
  • v3/:Wrangler(miniflare)的持久化目录,含 cache/、d1/、workflows/ 子目录;Docker 形态下前端读写走 Express+SQLite,不依赖该目录,但它仍由 Wrangler 进程创建,保留即可

实测整个目录仅 296 KB,体量极小,直接整目录打包即可完成备份。

💡 说明:docker compose down 只删除容器与网络,不删宿主 ./data;再次 up -d 后收藏与播放状态原样恢复(本文 4.8 节已实测验证)。

六、环境变量表

环境变量默认值是否生效说明
PASSWORD空(开放访问)生效全站访问口令;留空则任何人可直接进入,未登录访问首页不再重定向
API_BASE_URLhttps://music-api.gdstudio.xyz/api.php生效上游聚合 API 地址,搜索/歌词/封面/直链都经它
language空(中文)写入成功,Docker 形态界面不切换界面语言,ENG 为英文;生效条件见第七章
PORT8787不生效(被入口脚本覆盖)入口脚本显式 export PORT=8787,容器内端口固定,改宿主映射才是正解
HOST0.0.0.0不生效(被入口脚本覆盖)同上,入口脚本 export HOST=0.0.0.0
DATA_DIR/data不生效(被入口脚本覆盖)入口脚本 export DATA_DIR=/data,持久化目录固定为 /data
WRANGLER_API_URLhttp://127.0.0.1:8788由入口脚本注入Express 转发 /proxy 时使用的内部地址,无需手工设置

⚠️ 实测提醒:PORT/HOST/DATA_DIR 虽然 Express 代码支持从环境变量读取,但容器入口脚本在启动前无条件 export 了固定值,因此这三个变量在 Docker 形态下改不动。要换对外端口只能改编排文件的 ports 左侧值;要换数据目录只能改 volumes 的映射目标(不建议,/data 是镜像内约定路径)。

七、配置参数说明

7.1 PASSWORD 与开放访问

PASSWORD 决定是否启用认证。逻辑是:未配置(空)时全部放行;配置后校验 Cookie auth 是否等于口令的 Base64。

  • 设置口令(本文做法):登录页输入口令 → 下发 48 小时 Cookie → 首页可访问
  • 清空口令:删除编排文件中 PASSWORD 行 → docker compose up -d 重建 → 首页直接 200,日志横幅显示 Password: ✗ not set (open access)
  • 改口令:改 PASSWORD 值并重建容器,所有旧 Cookie 立即失效(Cookie 值随口令变化)

7.2 API_BASE_URL 与上游切换

默认指向 GD音乐台免费聚合 API。上游出现持续 Challenge/超时时,可换备用地址后重建容器:

# 编排文件中改为备用地址后重建
docker compose up -d --force-recreate

验证是否切换成功:

curl -s -b /opt/solara/cookies.txt --max-time 30 \
  "http://127.0.0.1:8136/proxy?types=search&source=netease&name=test&count=1&pages=1" \
  -w "\nHTTP %{http_code}\n"

实测输出(正常形态):

HTTP 200

⚠️ 故障注入实测:把 API_BASE_URL 指向不可达地址并重建后,搜索接口返回 502(Express 侧 Upstream proxy error),页面搜索无结果。恢复为正确地址重建即恢复。

7.3 language 在 Docker 形态下不生效(docs vs reality)

这是本项目最值得注意的「文档声称支持、实测不生效」项:

  • 官方 README 称 Docker 部署时设置 language=ENG 即可切换英文界面
  • 实测:入口脚本确实把 language 写进了 /app/.dev.vars(见第八章回显),Wrangler 侧的中间件也确实会在 HTML <head> 注入 window.SITE_LANGUAGE = "ENG"
  • 但 Docker 形态的页面由 Express 直接 sendFile 返回(server/index.js 的 GET / 与 GET /login),绕过了 Wrangler 中间件,浏览器拿到的 HTML 里没有该注入
  • 实测结果:经 8787 端口取回的页面中 window.SITE_LANGUAGE 出现次数为 0;直连容器内 8788 端口(Wrangler)取回的页面出现次数为 1
  • 结论:英文界面只在 Cloudflare Pages 部署形态生效;Docker 形态保留 language=ENG 不报错但界面仍是中文

7.4 缓存参数

  • /proxy 上游响应缓存 5 分钟:搜索结果、歌词、直链共用;命中时响应头 X-Cache-Status: HIT
  • 缓存排除:结果为空([])、含 "error"/"status":0 的响应不写缓存;URL 中带 nocache=true 时强制回源
  • /palette 封面调色板结果缓存 1 小时,减少对封面图的重复下载与解码

验证缓存是否命中:

curl -s -b /opt/solara/cookies.txt -D h1.txt -o /dev/null \
  "http://127.0.0.1:8136/proxy?types=search&source=netease&name=%E5%91%A8%E6%9D%B0%E4%BC%A6&count=5&pages=1"
curl -s -b /opt/solara/cookies.txt -D h2.txt -o /dev/null \
  "http://127.0.0.1:8136/proxy?types=search&source=netease&name=%E5%91%A8%E6%9D%B0%E4%BC%A6&count=5&pages=1"
grep -i 'x-cache-status' h1.txt h2.txt

实测输出(第一次回源、第二次命中):

h1.txt:X-Cache-Status: MISS
h2.txt:X-Cache-Status: HIT

八、配置文件挂载说明

Solara 没有外部配置文件(不像 Navidrome 的 TOML/Redis 的 conf),全部配置项只有上面 3 个环境变量。容器内存在一个由入口脚本自动生成的文件 /app/.dev.vars,供 Wrangler 读取:

docker exec solara-v2.1.6-app cat /app/.dev.vars

实测输出:

PASSWORD="q1w2e3r4"
API_BASE_URL="https://music-api.gdstudio.xyz/api.php"
language="ENG"

逐行说明:

  • 三行分别对应编排文件 environment 的三个变量;值为空或未设置的变量不会写入该文件
  • 文件每次容器启动时被清空重写(入口脚本先 : > "$DEV_VARS"),因此手工修改容器内该文件在重启后会丢失
  • 若确有特殊需求要自定义,正确的做法是改编排文件的 environment,而不是挂载覆盖该文件——挂载覆盖会与入口脚本的「清空重写」逻辑冲突,导致变量丢失

💡 说明:这就是本项目「配置即环境变量」的设计,也意味着没有配置文件热加载——任何参数变更都要 docker compose up -d(必要时 --force-recreate)重建容器。

九、等价 docker run 命令

不使用 Compose 时,先建好独立网络(与编排中的 solara-net 等价,本文实测用 10.89.66.0/24 避免与已部署栈冲突):

docker network create -d bridge --subnet 10.89.66.0/24 solara-run-net

实测输出:

bdde77a69d0188792ce2dbb0ffb0f66a3825ec74bd11601f11f8b91ab204fe76

再启动容器(已实测跑通,宿主端口用空闲的 8141):

docker run -d \
  --name solara-v2.1.6-app \
  --restart unless-stopped \
  --init \
  -p 8141:8787 \
  -e PASSWORD=q1w2e3r4 \
  -e API_BASE_URL=https://music-api.gdstudio.xyz/api.php \
  -e language=ENG \
  -v /opt/solara/data:/data \
  --network solara-run-net \
  ghcr.io/akudamatata/solara:2.1.6

实测输出:

506ce0e73ea150b6e79864111414e6262088aa4fb0cb6194e31a76c644d8a47c

容器创建后探活:

curl -s -o /dev/null -w "GET / -> %{http_code} redirect=%{redirect_url}\n" http://127.0.0.1:8141/

实测输出:

GET / -> 302 redirect=http://127.0.0.1:8141/login

启动日志尾部(与 Compose 形态一致):

  🌟 Solara Standalone Server
  ─────────────────────────────────────
  Ready on http://0.0.0.0:8787
  Password: ✓ configured
  ─────────────────────────────────────

参数与编排文件一一对应:-p 端口映射、-e 环境变量、-v 数据卷、--network 接入网络、--init 对应 init: true。长期运行仍建议用 Compose——编排即文档,改动可追溯。

十、常用运维命令

以下命令均在 /opt/solara/(编排文件所在目录)执行。

查看实时日志:

docker logs -f --tail 100 solara-v2.1.6-app

查看服务状态:

docker compose ps

实测输出:

NAME                IMAGE                              COMMAND                  SERVICE   CREATED          STATUS          PORTS
solara-v2.1.6-app   ghcr.io/akudamatata/solara:2.1.6   "/app/docker-entrypo…"   solara    14 minutes ago   Up 14 minutes   0.0.0.0:8136->8787/tcp, [::]:8136->8787/tcp

重启服务(改环境变量后必须用 up -d 重建,restart 不会重读编排):

docker compose restart

实测输出:

 Container solara-v2.1.6-app Restarting 
 Container solara-v2.1.6-app Started 

重建容器(环境变量/端口变更后):

docker compose up -d

实测输出(无变化时):

 Container solara-v2.1.6-app Running 

停用服务(保留数据):

docker compose down

实测输出:

 Container solara-v2.1.6-app Stopping 
 Container solara-v2.1.6-app Stopped 
 Container solara-v2.1.6-app Removing 
 Container solara-v2.1.6-app Removed 
 Network solara-v216-stack_solara-net Removing 
 Network solara-v216-stack_solara-net Removed 

再次启动:

docker compose up -d

实测输出:

 Network solara-v216-stack_solara-net Creating 
 Network solara-v216-stack_solara-net Created 
 Container solara-v2.1.6-app Creating 
 Container solara-v2.1.6-app Created 
 Container solara-v2.1.6-app Starting 
 Container solara-v2.1.6-app Started 

服务重启后探活:

curl -s -o /dev/null -w "GET /login -> %{http_code}\n" http://127.0.0.1:8136/login

实测输出:

GET /login -> 200

💡 说明:down 只删容器与项目网络,./data 目录与其中的 SQLite 数据库完整保留;再次 up -d 即恢复全部收藏与播放状态。

升级到新版本:把编排文件 image 标签改为新 tag,然后:

docker compose pull && docker compose up -d

实测输出(pull 段):

 Image ghcr.io/akudamatata/solara:2.1.6 Pulling 
 Image ghcr.io/akudamatata/solara:2.1.6 Pulled 

回滚:改回上一版本 tag(如 2.1.5)后同样 docker compose up -d。实测 2.1.5 ↔ 2.1.6 互切后 data/ 数据无损(同一 SQLite 表结构)。

十一、常见问题

Q1:docker compose up -d 报端口已被占用(port is already allocated)?

先查占用方:ss -ltnp | grep 8136。占用方是别的服务就改编排文件 ports 左侧值;占用方是残留的旧 Solara 容器则 docker compose down 后重来(本文实测 down 会一并删掉项目网络,不会残留)。

Q2:编排里写 name: solara-v2.1.6-stack,为什么 docker network ls 里看到的是 solara-v216-stack_solara-net?

Docker 会把 Compose 项目名规范化为「小写字母+数字+连字符」,点号被剥除。solara-v2.1.6-stack → solara-v216-stack。验证方式:

docker compose config --format json | python3 -c "import json,sys; print('name=', json.load(sys.stdin)['name'])"

实测输出:

name= solara-v216-stack

建议直接按规范化后的名字写 name:。

Q3:搜索一直无结果,或提示接口繁忙?

按顺序排查:① 访问控制是否通过(未登录时 /proxy 返回 401 JSON {"error":"Unauthorized"});② 上游可达性(curl 直连 API_BASE_URL 看是否 200);③ 免费 API 波动——实测同日热歌榜接口曾 180 秒超时(HTTP 000),几分钟后恢复属常态;④ 换 source 试,实测仅 netease 可用。

Q4:选了「酷我」源报错?

实测上游已不支持:{"detail":"Value of source is not supported."}(HTTP 400)。这不是部署问题,属上游能力收缩,选回 netease 即可。

Q5:设置了 language=ENG,界面还是中文?

见第七章 7.3:Docker 形态下页面由 Express 直接发送、绕过 Wrangler 的 HTML 注入中间件,故不生效;仅 Cloudflare Pages 形态支持。这不是配置错误。

Q6:docker stop 要等十秒才停?

确认编排里 init: true 在位。缺少该项时容器内 Node + Wrangler 两个进程收不到 SIGTERM 传播,Docker 只能等默认 10 秒超时后 SIGKILL;加上后停止通常是秒级。

Q7:启动日志里能看到访问口令明文?

是的,属实测确认的缺陷(见 4.4 节安全警示)。缓解措施:限制 docker logs 访问权限、日志采集侧对 Password : 行做过滤、并把口令当作已泄漏凭据管理(在可信内网使用、不做外网直接暴露)。

Q8:容器启动后短暂出现 ECONNREFUSED 127.0.0.1:8788?

入口脚本启动 Wrangler 后 sleep 2 就拉起 Express,机器负载高时 Wrangler 可能尚未就绪,首个 /proxy 请求会命中连接拒绝,稍后自动恢复(实测重建后出现过一次,后续请求正常)。不需要干预;持续出现则查 docker logs 中 wrangler 的启动失败原因。

十二、参考链接