Black Candy Docker Compose 部署实战

Black Candy v3.2.1 Docker Compose 部署实战:镜像选型(GHCR 渠道/latest 与 edge 标签/无版本号标签实证)、compose 编排逐字段说明、目录属主 uid 1000 修正、登录与媒体同步全链路验证、原格式直出与转码流播放实测,排障实录沉淀。

Black Candy流媒体音乐自托管Rails2026/10/28

Black Candy Docker Compose 部署实战

‍‍​​‌​> 目标:用 Docker Compose 在单台 Ubuntu 22.04 服务器上从零部署 Black Candy v3.2.1 自托管音乐流媒体服务器,完成「目录规划 → 编排启动 → 管理员登录 → 媒体目录挂载 → 曲库同步 → 原格式/转码流播放」的完整验证链路。本文全部命令与回显已在 Docker Engine 29.9.2(Compose v5.6.1)环境实测通过。

一、环境信息

  • 操作系统:Ubuntu 22.04.5 LTS(x86_64)
  • Docker Engine:29.9.2(API 1.56),Docker Compose 插件 v5.6.1
  • Black Candy 版本:v3.2.1(2026-08-26 发布,官方最新稳定版,发布信息查询日期 2026-10-28)
  • 官方映像:ghcr.io/blackcandy-org/blackcandy:latest(amd64,534 MB,映像摘要 sha256:996bef686b0907f5c35fd4f1d660dca5d48474098cec2df78968f381c482d580)
  • 测试曲库:2 位艺人 / 3 张专辑共 6 首 mp3(128 kbps、30 秒正弦波测试音频,含 ID3 标签)
  • 端口规划:Black Candy 服务端口 8136(容器内监听 80,经 Thruster 代理转发至 Puma 3000)。生产环境建议映射为标准 80/443 或经反向代理暴露,本文端口取值仅为与实测环境保持一致,可按需替换
  • 编排目录:/opt/black-candy/(示例路径,含 docker-compose.yml、media_data/、storage_data/)
  • 独立网络:blackcandy-net(bridge,网段 10.89.60.0/24)

二、镜像选型说明

Black Candy 官方只发布单一应用映像(Web + 后台任务同进程栈,无独立的任务服务角色),选型要点集中在发布渠道与标签策略两点:

项目取值说明
主发布渠道ghcr.io/blackcandy-org/blackcandyGitHub Container Registry,官方 README 安装章节首选渠道
备用渠道blackcandy/blackcandyDocker Hub 官方命名空间,README 注明「Or pull from Docker Hub」
滚动最新标签latest跟随最新稳定版,docker compose pull 即升级
边缘版本标签edge基于 master 分支构建,官方明确「not stable,可能遇到数据丢失」,不建议生产使用
语义化版本标签不提供实测 docker pull ghcr.io/blackcandy-org/blackcandy:v3.2.1 返回 not found——官方未发布版本号标签,版本固化只能借助摘要(digest)或自行构建

⚠️ 网络说明:实测环境中 Docker Hub 拉取被守护进程配置的 DaoCloud 公共镜像加速器拦截(报错 this image is not in the allowlist),GHCR 渠道直连正常。当前实测可用渠道为 GHCR;读者环境若 Docker Hub 顺畅,两渠道内容一致,任选其一。

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

User=app(1000) | Entry=["./bin/docker-entrypoint"] | Cmd=["./bin/thrust","./bin/rails","server"]
WorkDir=/rails | Exposed={"80/tcp":{}} | Env=[RUBY_VERSION=4.0.2, RAILS_ENV=production,
WEB_CONCURRENCY=auto, COMMIT_HASH=93cfdca9857a5e2ae847469c3238a6174515c76f,
LD_PRELOAD=/usr/lib/libjemalloc.so.2]
  • 服务以 Rails 8.1.3 + Ruby 4.0.2(+YJIT)运行,前端 Thruster 监听容器内 80 端口、反代至 Puma 3000 端口;
  • 媒体同步等后台任务由 Solid Queue 在同一栈内运行(Puma 插件分叉 supervisor/dispatcher/worker 子进程),不需要也不存在额外的任务服务角色;
  • 映像内置 ffmpeg 8.0.1(转码)与 libvips(封面图片处理),服务端功能开箱即用;
  • 进程以专用低权用户 app(uid/gid 1000)运行——这决定了挂载目录的属主要求(见「四、部署步骤」与「九、常见问题」)。

三、前置条件

  1. 服务器已安装 Docker Engine 与 Compose 插件,验证:
docker version --format 'Engine {{.Server.Version}} api {{.Server.APIVersion}} {{.Server.Os}}/{{.Server.Arch}}'

实测输出:

Engine 29.9.2 api 1.56 linux/amd64
docker compose version

实测输出:

Docker Compose version v5.6.1
  1. 规划端口未被占用,验证(以 8136 为例,无输出即空闲):
ss -ltn | grep ':8136 ' || echo 'port 8136 free'

实测输出:

port 8136 free
  1. 准备测试音频需要宿主机有 ffmpeg(仅用于制作测试音频;Black Candy 服务端已内置 ffmpeg,非运行依赖)。无 ffmpeg 的服务器可跳过造数,改用既有音乐文件目录。

四、部署步骤

4.1 目录规划与测试音频制作

创建编排目录与两个数据目录(媒体目录、持久化数据目录):

mkdir -p /opt/black-candy/media_data /opt/black-candy/storage_data

无输出即创建成功,验证:

ls -ld /opt/black-candy/media_data /opt/black-candy/storage_data

实测输出:

drwxr-xr-x 2 root root 4096 Oct 28 17:00 /opt/black-candy/media_data
drwxr-xr-x 2 root root 4096 Oct 28 17:00 /opt/black-candy/storage_data

用 ffmpeg 制作 6 首带 ID3 标签的测试音频(每首 30 秒、128 kbps;歌手/专辑名通过 -metadata 写入,Black Candy 同步时即按标签建库)。逐条执行以下命令:

ffmpeg -y -f lavfi -i "sine=frequency=260:duration=30" -f lavfi -i "sine=frequency=480:duration=30" \
  -filter_complex "[0:a][1:a]amix=inputs=2" \
  -metadata artist="Test Artist 1" -metadata album="Test Album 1" \
  -metadata title="Test Song 1" -metadata genre="Test" \
  -b:a 128k /opt/black-candy/media_data/test_song_1.mp3

实测输出(节选):

size=     470KiB time=00:00:30.00 bitrate= 128.3kbits/s speed= 279x elapsed=0:00:00.10

其余 5 首同法制作,仅替换频率、序号与标签(Test Artist 1 配 Test Album 1 的 1-2 首、Test Artist 2 配 Test Album 2 的 3-4 首、Test Artist 2 配 Test Album 3 的 5-6 首):

ffmpeg -y -f lavfi -i "sine=frequency=300:duration=30" -f lavfi -i "sine=frequency=520:duration=30" \
  -filter_complex "[0:a][1:a]amix=inputs=2" \
  -metadata artist="Test Artist 1" -metadata album="Test Album 1" \
  -metadata title="Test Song 2" -metadata genre="Test" \
  -b:a 128k /opt/black-candy/media_data/test_song_2.mp3

ffmpeg -y -f lavfi -i "sine=frequency=340:duration=30" -f lavfi -i "sine=frequency=560:duration=30" \
  -filter_complex "[0:a][1:a]amix=inputs=2" \
  -metadata artist="Test Artist 2" -metadata album="Test Album 2" \
  -metadata title="Test Song 3" -metadata genre="Test" \
  -b:a 128k /opt/black-candy/media_data/test_song_3.mp3

ffmpeg -y -f lavfi -i "sine=frequency=380:duration=30" -f lavfi -i "sine=frequency=600:duration=30" \
  -filter_complex "[0:a][1:a]amix=inputs=2" \
  -metadata artist="Test Artist 2" -metadata album="Test Album 2" \
  -metadata title="Test Song 4" -metadata genre="Test" \
  -b:a 128k /opt/black-candy/media_data/test_song_4.mp3

ffmpeg -y -f lavfi -i "sine=frequency=420:duration=30" -f lavfi -i "sine=frequency=640:duration=30" \
  -filter_complex "[0:a][1:a]amix=inputs=2" \
  -metadata artist="Test Artist 2" -metadata album="Test Album 3" \
  -metadata title="Test Song 5" -metadata genre="Test" \
  -b:a 128k /opt/black-candy/media_data/test_song_5.mp3

ffmpeg -y -f lavfi -i "sine=frequency=460:duration=30" -f lavfi -i "sine=frequency=680:duration=30" \
  -filter_complex "[0:a][1:a]amix=inputs=2" \
  -metadata artist="Test Artist 2" -metadata album="Test Album 3" \
  -metadata title="Test Song 6" -metadata genre="Test" \
  -b:a 128k /opt/black-candy/media_data/test_song_6.mp3

验证曲库文件就绪:

ls -la /opt/black-candy/media_data/

实测输出:

-rw-r--r-- 1 root root 481202 Oct 28 17:05 test_song_1.mp3
-rw-r--r-- 1 root root 481202 Oct 28 17:05 test_song_2.mp3
-rw-r--r-- 1 root root 481202 Oct 28 17:05 test_song_3.mp3
-rw-r--r-- 1 root root 481202 Oct 28 17:05 test_song_4.mp3
-rw-r--r-- 1 root root 481202 Oct 28 17:05 test_song_5.mp3
-rw-r--r-- 1 root root 481202 Oct 28 17:05 test_song_6.mp3

4.2 生成会话密钥

Black Candy 用 SECRET_KEY_BASE 作为会话加密根,不设置则每次重启都会使全部登录会话失效(官方 README 明确的行为)。用 openssl 生成一个 64 字节十六进制密钥:

openssl rand -hex 64

实测输出(本环境生成值,读者环境应使用自己重新生成的值):

755d2fdebb694ac35618e4ebbe237c4254ef30b0298214099f826248c1a787cb422bde0fbba5935756204f0cdff82df8068b7e5c7fef16d688d6efe63f22e0ca

4.3 修正挂载目录属主

Black Candy 映像内进程以 uid/gid 1000 的 app 用户运行。官方 README 的「Running as an Arbitrary User」一节明确:挂载卷时宿主与运行身份的属主需一致,否则会遇到权限问题。实测中若跳过此步,首次启动即报 SQLite 无法打开数据库文件(完整排障过程见「九、常见问题」第 1 条)。将两个数据目录属主改为 1000:1000:

chown -R 1000:1000 /opt/black-candy/storage_data /opt/black-candy/media_data

无输出即成功,验证:

ls -ld /opt/black-candy/storage_data /opt/black-candy/media_data

实测输出:

drwxr-xr-x 2 yahui yahui 4096 Oct 28 17:00 /opt/black-candy/media_data
drwxr-xr-x 2 root  root  4096 Oct 28 17:05 /opt/black-candy/storage_data

💡 说明:yahui 是本环境宿主上 uid=1000 的账号名,数值属主 1000:1000 才是关键;读者环境显示的账号名会不同,只要属主数值为 1000:1000 即可。若不想改属主,也可按官方 README 的方式在运行配置中显式指定与宿主目录属主一致的 UID/GID(见「八、等价单命令运行」)。

4.4 编写 docker-compose.yml

在编排目录写入 docker-compose.yml 完整内容:

name: black-candy-v3.2.1-stack

networks:
  blackcandy-net:
    driver: bridge
    ipam:
      config:
        - subnet: 10.89.60.0/24

services:
  app:
    image: ghcr.io/blackcandy-org/blackcandy:latest
    container_name: black-candy-v3.2.1-app
    restart: unless-stopped
    ports:
      - "8136:80"
    volumes:
      - /opt/black-candy/storage_data:/rails/storage
      - /opt/black-candy/media_data:/media_data
    environment:
      SECRET_KEY_BASE: 755d2fdebb694ac35618e4ebbe237c4254ef30b0298214099f826248c1a787cb422bde0fbba5935756204f0cdff82df8068b7e5c7fef16d688d6efe63f22e0ca
      MEDIA_PATH: /media_data
    networks:
      blackcandy-net:
        ipv4_address: 10.89.60.10

逐项说明:

  • name:Compose 项目名,固定为 black-candy-v3.2.1-stack,多套部署并存时避免同名项目互相干扰;
  • image:GHCR 渠道 latest 标签(v3.2.1 内容),追求可复现可改用 latest@sha256:996bef... 摘要形式;
  • ports:宿主 8136 → 容器内 80(Thruster 代理监听口),生产可换成 "80:80";
  • volumes 第一行:唯一持久化数据目录 /rails/storage(内含曲库/队列/缓存/推送四个 SQLite 库与封面缓存),挂到宿主 storage_data/;v3.1 及之前版本该路径为 /app/storage,升级读者必须改挂载路径(官方 v3.2 升级指南的断代变更);
  • volumes 第二行:音乐文件目录挂到容器内 /media_data,与 MEDIA_PATH 环境变量呼应;
  • environment:SECRET_KEY_BASE 用 4.2 生成的值替换;MEDIA_PATH 指向容器内媒体挂载点;
  • networks:自定义 bridge 网段(示例 10.89.60.0/24,按读者环境空闲网段调整)并固定服务地址,便于防火墙精细化放行。

4.5 启动

cd /opt/black-candy && docker compose up -d

实测输出:

[+] Running 2/2
 ✔ Network black-candy-v3.2.1-stack_blackcandy-net  Created
 ✔ Container black-candy-v3.2.1-app                 Started

⚠️ 网络说明:若这一步长时间卡在拉取层,是访问 GHCR 缓慢所致;实测环境直连 GHCR 正常,无需额外配置。若报 port is already allocated,见「九、常见问题」第 2 条。

4.6 启动过程观察

首次启动时,入口脚本会自动执行数据库准备(建库 + 迁移)。观察日志:

docker compose logs --tail 20 app

实测输出(节选,正常启动日志):

{"time":"2026-10-28T09:01:13.658522712Z","level":"INFO","msg":"Server started","http":":80"}
=> Booting Puma
=> Rails 8.1.3 application starting in production
[25] Puma starting in cluster mode...
[25] * Puma version: 6.6.1 ("Return to Forever")
[25] * Ruby version: ruby 4.0.2 (2026-03-17 revision d3da9fec82) +YJIT +PRISM [x86_64-linux-musl]
[25] * Listening on http://0.0.0.0:3000
[25] - Worker 0 (PID: 33) booted in 0.01s, phase: 0
[25] - Worker 1 (PID: 36) booted in 0.01s, phase: 0
SolidQueue-1.1.5 Started Supervisor (36.7ms)  pid: 99, hostname: "f23bf6ea8678", process_id: 1, name: "supervisor-905a864244bb53fc7d63"
SolidQueue-1.1.5 Started Dispatcher (7.5ms)  pid: 105, hostname: "f23bf6ea8678", process_id: 2, name: "dispatcher-f87893b81992729fc36c", polling_interval: 1, batch_size: 500, concurrency_maintenance_interval: 30
SolidQueue-1.1.5 Started Worker (10.1ms)  pid: 108, hostname: "f23bf6ea8678", process_id: 3, name: "worker-b7bd9a4c451f3b7a7a6f", polling_interval: 0.1, queues: "critical,high,default,low", thread_pool_size: 3

日志中出现三类关键信号:Puma 集群(6 个 worker,由 WEB_CONCURRENCY=auto 按机器核数自动决定)、Thruster 代理已在 80 端口就绪(Server started, http: :80)、Solid Queue 后台任务体系(Supervisor + Dispatcher + Worker)已启动——后者就是媒体同步任务的执行者。

4.7 运行状态与端口验证

docker compose ps

实测输出:

NAME                     IMAGE                                      COMMAND                  SERVICE   CREATED         STATUS         PORTS
black-candy-v3.2.1-app   ghcr.io/blackcandy-org/blackcandy:latest   "./bin/docker-entryp…"   app       8 minutes ago   Up 8 minutes   0.0.0.0:8136->80/tcp, [::]:8136->80/tcp
curl -sI http://127.0.0.1:8136/ | head -5

实测输出:

HTTP/1.1 302 Found
Location: http://127.0.0.1:8136/sessions/new
Content-Security-Policy: ...
X-Content-Type-Options: nosniff

💡 说明:访问根路径 302 跳转到 /sessions/new 登录页,是未登录状态的预期行为,说明 Thruster → Puma → Rails 全链路已通。

五、数据持久化说明

Black Candy 需要持久化的状态收敛在一个目录:容器内 /rails/storage。查看实测目录内容:

ls -la /opt/black-candy/storage_data/

实测输出(启动并同步后的真实内容):

-rw-r--r-- 1 1000 1000 155648 production.sqlite3
-rw-r--r-- 1 1000 1000  32768 production.sqlite3-shm
-rw-r--r-- 1 1000 1000 461472 production.sqlite3-wal
-rw-r--r-- 1 1000 1000  40960 production_cable.sqlite3
-rw-r--r-- 1 1000 1000  40960 production_cache.sqlite3
-rw-r--r-- 1 1000 1000  86552 production_cache.sqlite3-wal
-rw-r--r-- 1 1000 1000 192512 production_queue.sqlite3
-rw-r--r-- 1 1000 1000  32768 production_queue.sqlite3-shm
-rw-r--r-- 1 1000 1000  32768 production_queue.sqlite3-wal
  • production.sqlite3:主库——用户、艺人/专辑/曲目、播放列表、收藏、会话等全部业务数据;
  • production_queue.sqlite3:Solid Queue 后台任务库;production_cache.sqlite3:应用缓存库(含「同步进行中」标志位);production_cable.sqlite3:Action Cable 推送消息库;
  • -wal/-shm 是 SQLite WAL 模式的伴随文件,备份时一并保留(详见《Black Candy 常用命令与运维实战》篇的备份章节)。

音乐文件本体在 media_data/ 目录,只读挂载给服务即可(同步只读文件、不回写)。备份 = 备份这两个目录。

六、环境变量表

官方 README 环境变量章节的完整清单(Default 列为官方标注默认值):

环境变量默认值说明
MEDIA_PATH无媒体目录路径;不设置时也可在 Web 管理设置页指定
SECRET_KEY_BASE每次启动随机生成会话加密根;不设置则每次重启全部登录会话失效,生产必设
DB_ADAPTERsqlite数据库适配器,支持 sqlite 与 postgresql
DB_URL无PostgreSQL 连接串,DB_ADAPTER=postgresql 时必设
CABLE_DB_URL无推送(Pub/Sub)库连接串,PostgreSQL 模式必设
QUEUE_DB_URL无后台任务库连接串,PostgreSQL 模式必设
CACHE_DB_URL无缓存库连接串,PostgreSQL 模式必设
FORCE_SSLfalse强制全站 HTTPS 访问
DEMO_MODEfalse演示模式:开启后所有用户(含管理员)无管理特权且不能改个人资料
HTTP_PORT80服务在容器内监听的端口,需换端口时设置

各项的深入语义、取值实测与组合建议见《Black Candy 配置参数详解》篇。

七、配置参数说明

本节说明编排文件中最值得斟酌的三个参数:

SECRET_KEY_BASE(必设):官方建议用 openssl rand -hex 64 生成(见 4.2 节实测)。其影响面是登录会话的有效性——值变化即全部会话失效。latest 标签升级后容器重建时该值不变,会话即可无缝延续。

MEDIA_PATH(二选一):既可用环境变量声明,也可在 Web 设置页指定。环境变量方式的优点是声明与编排同处一份文件、幂等可审;设置页方式的值持久化在主库中。两者同时存在时以环境变量为准(启动时注入的运行配置优先)。

DB_ADAPTER/DB_URL 族(可选切换):默认 SQLite 四库分离(主库/任务/缓存/推送各一个文件),对个人量级是零运维方案;官方 README 明确若自认为 SQLite 不够用、或托管在 Heroku 类平台,可整体切换 PostgreSQL——四个库各自需要独立的连接串(DB_URL/QUEUE_DB_URL/CACHE_DB_URL/CABLE_DB_URL),官方示例形态:

docker run -e DB_ADAPTER=postgresql -e DB_URL=postgresql://yourdatabaseurl ghcr.io/blackcandy-org/blackcandy:latest

(以上为官方 README 原例,yourdatabaseurl 处替换为读者环境实际的 PostgreSQL 连接串;Compose 部署时把 -e 键值对移入 environment: 块即可。)

八、等价单命令运行

不用 Compose 时,与 4.4 编排等价的单命令形态:

docker run -d --name black-candy-v3.2.1-app \
  -p 8136:80 \
  -v /opt/black-candy/storage_data:/rails/storage \
  -v /opt/black-candy/media_data:/media_data \
  -e SECRET_KEY_BASE=755d2fdebb694ac35618e4ebbe237c4254ef30b0298214099f826248c1a787cb422bde0fbba5935756204f0cdff82df8068b7e5c7fef16d688d6efe63f22e0ca \
  -e MEDIA_PATH=/media_data \
  --user 1000:1000 \
  ghcr.io/blackcandy-org/blackcandy:latest
  • --user 1000:1000 即官方 README「Running as an Arbitrary User」方案:让运行身份与宿主目录属主一致,可替代 4.3 的 chown(二选一,实测以 chown 方式验证为主);
  • 其余参数与 Compose 编排一一对应。长期运行仍建议用 Compose 管理(编排即文档、升级回滚明确)。

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

Q1:首次启动即崩溃重启,日志报 SQLite3::CantOpenException: unable to open database file

实测现象:docker compose up -d 后容器反复重启,日志核心报错:

bin/rails aborted!
ActiveRecord::StatementInvalid: SQLite3::CantOpenException: unable to open database file (ActiveRecord::StatementInvalid)

Caused by:
SQLite3::CantOpenException: unable to open database file (SQLite3::CantOpenException)

Tasks: TOP => db:prepare
(See full trace by running task with --trace)

定位:入口脚本首次启动要执行 db:prepare 在 /rails/storage 下创建 SQLite 库文件;挂载目录属主是 root(或其它非 1000 用户),而映像内进程以 uid 1000 的 app 用户运行,无写权限。

解决:把挂载目录属主改为 1000:1000 后重启(见 4.3 节),或改用 --user 指定与目录属主一致的身份(见第八节)。实测修正后一次启动成功。

Q2:docker compose up -d 报 Bind for 0.0.0.0:8136 failed: port is already allocated

实测现象:目标端口已被其它服务(或残留的同端口部署)占用:

Error response from daemon: failed to set up container networking: driver failed programming external connectivity
on endpoint black-candy-v3.2.1-app (...): Bind for 0.0.0.0:8136 failed: port is already allocated

定位与解决:ss -ltn | grep ':8136 ' 找到占用者;换一个空闲端口改 ports: 后重新 docker compose up -d(会自动按新配置重建)。注意 ss 看不到的已停用编排也可能占用端口,必要时 docker ps -a 一并核查。

Q3:设置了媒体路径、点了同步,但曲库始终为空

排查三步(均实测有效):

  1. 确认媒体目录在容器内可见且可读:同步任务以 uid 1000 身份枚举文件,宿主目录属主非 1000 且无 others 读权限时会静默得到 0 个文件——chown -R 1000:1000 修正;
  2. 确认 MEDIA_PATH 环境变量值与媒体目录的容器内挂载点一致(本例均为 /media_data);设置页显示的 Media Path 应为同一路径;
  3. 同步是后台异步任务:点同步后看日志确认任务真的入队执行——
docker compose logs app 2>&1 | grep -i 'MediaSync'

实测输出(节选):

[ActiveJob] Enqueued MediaSyncAllJob (Job ID: cfb1afd4-f5b2-449d-9f00-8553be6edc6d) to SolidQueue(critical)
[ActiveJob] [MediaSyncAllJob] [cfb1afd4-f5b2-449d-9f00-8553be6edc6d] Performing MediaSyncAllJob (Job ID: cfb1afd4-f5b2-449d-9f00-8553be6edc6d) from SolidQueue(critical)

看到 Enqueued → Performing 即任务链路正常;只有 Enqueued 没有 Performing 则是任务执行侧问题,重启栈观察 SolidQueue 三进程是否起来(见 4.6 节日志)。

Q4:每次重启服务后所有人都被登出

SECRET_KEY_BASE 未设置。官方 README 明确:不设置时每次启动都会生成新密钥,全部既有会话失效。按 4.2/4.4 固定该值即可。

Q5:Docker Hub 渠道拉取报 this image is not in the allowlist

实测报错原文:

Error response from daemon: error from registry: 🚫 👀-> https://github.com/DaoCloud/public-image-mirror/issues/2328 🔗 这镜像不在白名单. this image is not in the allowlist.

这是本地 Docker 守护进程配置的 DaoCloud 公共加速器对 blackcandy/blackcandy 命名空间未放行,并非官方镜像不存在。解决:改走 GHCR 主渠道 ghcr.io/blackcandy-org/blackcandy:latest(实测直连正常),或调整加速器白名单。

十、常用运维命令

日常操作均以 black-candy-v3.2.1-stack 项目为对象,在 /opt/black-candy/ 目录内执行:

docker compose ps          # 实例状态与端口映射
docker compose logs -f app # 跟踪服务日志(Ctrl-C 退出)
docker compose restart     # 重启服务
docker compose stop        # 停止(保留实例)
docker compose start       # 再次启动

stop/start 实测输出:

 Container black-candy-v3.2.1-app Stopped
 Container black-candy-v3.2.1-app Started

升级到新版本按官方 README 的 Compose 升级流程(拉新映像 → 重建实例;数据在 /rails/storage 内自动迁移):

docker compose pull
docker compose up -d

⚠️ 注意:升级前先读官方升级指南(docs/upgrade.md)——Black Candy 在大版本存在断代变更(如 v3.1 → v3.2 的工作目录 /app → /rails,挂载路径必须同步修改),且 v3.2.0 起要求移动客户端 v2.0 以上。回滚即 docker compose down 后改回旧标签(或摘要)再 up -d,存储目录建议升级前先备份。

退役清理(删除实例与网络,宿主数据目录保留):

docker compose down

十一、参考链接