1. 构建并推送自定义镜像到阿里云 ACR
- 你已经在项目根目录(有 docker-compose.yml 的位置),并且各服务的 Dockerfile 路径如下:
bash
docker/comfy-lipsync/Dockerfile
docker/cosyvoice/Dockerfile
docker/whisper-service/Dockerfile
第一步:登录阿里云容器镜像服务
bash
docker login --username=aalliiyy registry.cn-hangzhou.aliyuncs.com
第二步:为每个镜像重新打标签
标签格式:
bash
registry.cn-hangzhou.aliyuncs.com/<命名空间>/<仓库名>:<版本号>
你的命名空间:xxx
你的仓库:xxx_repository
注意:同一个仓库可以存放多个不同镜像(通过不同 tag 区分),所以标签可以像下面这样:
bash
# comfy-lipsync
# comfy-lipsync
docker tag flower-ai/comfy-lipsync:latest registry.cn-hangzhou.aliyuncs.com/flower_shine_raod/flower_shine_raod_repository:comfy-lipsync-v1.0
# cosyvoice
docker tag flower-ai/cosyvoice:latest registry.cn-hangzhou.aliyuncs.com/flower_shine_raod/flower_shine_raod_repository:cosyvoice-v1.0
# whisper-service
docker tag flower-ai/whisper-service:1.0.0 registry.cn-hangzhou.aliyuncs.com/flower_shine_raod/flower_shine_raod_repository:whisper-v1.0
# ollama-deepseek
docker tag flower-ai/ollama-deepseek:v1.0 registry.cn-hangzhou.aliyuncs.com/flower_shine_raod/flower_shine_raod_repository:ollama-deepseek-v1.0
如果这些镜像还没有构建,你需要先在项目根目录执行 docker compose build comfy-lipsync cosyvoice whisper-service 来生成。
第三步:推送镜像到 ACR
bash
docker push registry.cn-hangzhou.aliyuncs.com/flower_shine_raod/flower_shine_raod_repository:comfy-lipsync-v1.0
docker push registry.cn-hangzhou.aliyuncs.com/flower_shine_raod/flower_shine_raod_repository:cosyvoice-v1.0
docker push registry.cn-hangzhou.aliyuncs.com/flower_shine_raod/flower_shine_raod_repository:whisper-v1.0
docker push registry.cn-hangzhou.aliyuncs.com/flower_shine_raod/flower_shine_raod_repository:ollama-deepseek-v1.0
2. 客户拉取镜像后,如何启动服务并发起 API 请求。
拉取镜像
bash
docker pull registry.cn-hangzhou.aliyuncs.com/flower_shine_raod/flower_shine_raod_repository:comfy-lipsync-v1.0
docker pull registry.cn-hangzhou.aliyuncs.com/flower_shine_raod/flower_shine_raod_repository:cosyvoice-v1.0
docker pull registry.cn-hangzhou.aliyuncs.com/flower_shine_raod/flower_shine_raod_repository:whisper-v1.0
docker pull registry.cn-hangzhou.aliyuncs.com/flower_shine_raod/flower_shine_raod_repository:ollama-deepseek-v1.0
客户运行服务(以 comfy-lipsync 为例)
bash
docker run -d --gpus all \
-p 8189:8189 \
-v /path/to/your/models:/comfyui/models \
-v /path/to/audio:/comfyui/input/audio \
-v /path/to/output:/comfyui/output \
-e NVIDIA_VISIBLE_DEVICES=all \
registry.cn-hangzhou.aliyuncs.com/flower_shine_raod/flower_shine_raod_repository:comfy-lipsync-v1.0
启动后,服务监听 8189 端口。
服务的 API 端点
| 服务 | 端口 | 主要调用方式 |
|---|---|---|
| comfy-lipsync | 8189 | HTTP REST API(ComfyUI 格式),调用 /prompt 接口生成唇同步视频 |
| cosyvoice | 8003 | HTTP API ,用于语音合成,具体端点取决于你的实现(如 /generate) |
| whisper-service | 8004 | HTTP API ,上传音频文件进行语音识别,通常为 /asr 接口 |
3. 许可证镜像
3.1 创建许可证校验镜像
在项目 docker/license-guard/ 下新建两个文件:
- docker/license-guard/check_license.py
bash
#!/usr/bin/env python3
import os
import sys
import time
import requests
VERIFY_URL = "https://xxxx.com/license/check" # 替换为你的线上地址
RETRY_INTERVAL = 10 # 秒
MAX_RETRIES = 30 # 最多等 5 分钟
def verify_online(license_key):
"""调用线上 API 验证 License Key,返回 True/False"""
try:
resp = requests.post(
VERIFY_URL,
json={"license_key": license_key},
timeout=10
)
resp.raise_for_status()
data = resp.json()
return data.get("valid", False)
except Exception as e:
print(f"验证请求失败: {e}", flush=True)
return False
if __name__ == "__main__":
key = os.environ.get("LICENSE_KEY", "")
if not key:
print("❌ 未设置 LICENSE_KEY 环境变量", flush=True)
sys.exit(1)
# 重试逻辑:万一网络暂时不通,给几次机会
for i in range(1, MAX_RETRIES + 1):
print(f"正在进行许可证校验 ({i}/{MAX_RETRIES})...", flush=True)
if verify_online(key):
os.makedirs("/license", exist_ok=True)
with open("/license/valid.flag", "w") as f:
f.write("ok")
print("✅ 许可证验证通过,服务即将启动", flush=True)
# 保持容器运行,持续持有标记
while True:
time.sleep(600)
else:
print(f"❌ 验证失败,{RETRY_INTERVAL}秒后重试...", flush=True)
time.sleep(RETRY_INTERVAL)
print("❌ 超过最大重试次数,许可证无效。", flush=True)
sys.exit(1)
- docker/license-guard/Dockerfile
bash
FROM python:3.11-slim
RUN pip install requests
COPY check_license.py /check_license.py
RUN chmod +x /check_license.py
CMD ["python", "/check_license.py"]
3.2 构建并推送 license-guard 镜像
bash
cd docker/license-guard
docker build -t flower-ai/license-guard:v1.0 .
# 统一推送到你的 ACR 仓库
docker tag flower-ai/license-guard:v1.0 registry.cn-hangzhou.aliyuncs.com/flower_shine_raod/flower_shine_raod_repository:license-guard-v1.0
docker push registry.cn-hangzhou.aliyuncs.com/flower_shine_raod/flower_shine_raod_repository:license-guard-v1.0
现在你的私有仓库里有 5 个标签:
ollama-deepseek-v1.0, comfy-lipsync-v1.0, cosyvoice-v1.0, whisper-v1.0, license-guard-v1.0
3.3 创建 docker-compose.prod.yml
用你已经构建好的 ollama-deepseek-v1.0 替换掉官方 ollama/ollama,并加入 license-guard 及启动等待逻辑。
完整文件示例:
yaml
services:
# ==================== 许可证校验服务(新增)====================
license-guard:
image: registry.cn-hangzhou.aliyuncs.com/flower_shine_raod/flower_shine_raod_repository:license-guard-v1.0
container_name: license-guard
restart: "no" # 校验通过就退出?不,这里用持续运行便于管理,也可改为 on-failure
environment:
- LICENSE_KEY=${LICENSE_KEY} # 从 .env 文件读入
volumes:
- license_data:/license # 共享卷,存放标记文件
networks:
- flower-network
# ========== DeepSeek 本地模型服务(替换为你自己的镜像)==========
ollama-deepseek:
image: registry.cn-hangzhou.aliyuncs.com/flower_shine_raod/flower_shine_raod_repository:ollama-deepseek-v1.0
container_name: flower-deepseek-local
restart: unless-stopped
ports:
- "11434:11434"
environment:
- OLLAMA_KEEP_ALIVE=24h
dns:
- 8.8.8.8
- 223.5.5.5
volumes:
- license_data:/license:ro # 🆕 只读挂载许可证标记卷
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
# 🆕 启动前等待标记文件,最多等待 5 分钟
command: >
sh -c "
echo '等待许可证校验通过...' &&
for i in \$(seq 1 30); do
if [ -f /license/valid.flag ]; then
echo '许可证有效,启动 Ollama';
exec ollama serve;
fi;
sleep 10;
done;
echo '许可证无效或超时,退出';
exit 1
"
networks:
- flower-network
depends_on:
license-guard:
condition: service_healthy # 等 license-guard 健康运行后再启动?可简化为 depends_on 不加 condition,但需要确保 license-guard 先于它启动
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:11434/api/tags"]
interval: 30s
timeout: 10s
retries: 3
# ========== ComfyUI 唇同步服务 ==========
comfy-lipsync:
image: registry.cn-hangzhou.aliyuncs.com/flower_shine_raod/flower_shine_raod_repository:comfy-lipsync-v1.0
container_name: comfy-lipsync
ports:
- "8189:8189"
volumes:
- ./data/audio:/comfyui/input/audio
- ./data/models:/comfyui/models:ro
- ./data/models/reactor:/comfyui/models/reactor
- ./data/models/facedetection:/comfyui/models/facedetection
- ./data/input/lipsync:/comfyui/input
- ./data/output/lipsync:/comfyui/output
- ./data/workflows:/comfyui/workflows
- license_data:/license:ro # 🆕 共享卷
environment:
- NVIDIA_VISIBLE_DEVICES=all
- COMFYUI_MODELS_PATH=/comfyui/models
- PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:128
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
shm_size: '8gb'
mem_limit: '32g'
memswap_limit: '36g'
# 🆕 启动前等待许可证标记
command: >
sh -c "
echo '等待许可证...' &&
for i in \$(seq 1 30); do
if [ -f /license/valid.flag ]; then break; fi;
sleep 10;
done;
mkdir -p /comfyui/user/default &&
ln -sfn /comfyui/workflows /comfyui/user/default/workflows &&
python main.py --listen 0.0.0.0 --port 8189 --enable-cors-header --lowvram
"
depends_on:
- license-guard
networks:
- flower-network
# ========== CosyVoice 服务 ==========
cosyvoice:
image: registry.cn-hangzhou.aliyuncs.com/flower_shine_raod/flower_shine_raod_repository:cosyvoice-v1.0
container_name: cosyvoice
restart: unless-stopped
ports:
- "8003:8003"
environment:
TZ: Asia/Shanghai
HF_HOME: /cache/huggingface
TORCH_HOME: /cache/torch
XDG_CACHE_HOME: /cache/xdg
COSYVOICE_HOME: /opt/CosyVoice
COSYVOICE_MODEL_DIR: /models/cosyvoice/CosyVoice-300M-SFT
PYTHONPATH: /opt/CosyVoice:/opt/CosyVoice/third_party/Matcha-TTS
NVIDIA_VISIBLE_DEVICES: all
VOICE_PROFILE_DIR: /app/voice_profiles
VOICE_REF_DIR: /app/voice_refs
FLOWER_INTERNAL_TOKEN: ""
volumes:
- ./data/models:/models:ro
- ./data/cache/cosyvoice/huggingface:/cache/huggingface
- ./data/cache/cosyvoice/torch:/cache/torch
- ./data/cache/cosyvoice/xdg:/cache/xdg
- ./data/output/cosyvoice:/app/audio
- ./data/voice_refs:/app/voice_refs
- ./data/voice_profiles/cosyvoice:/app/voice_profiles
- license_data:/license:ro # 🆕 共享卷
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
shm_size: "8gb"
mem_limit: "16g"
# 🆕 启动前等待许可证
command: >
sh -c "
echo '等待许可证校验...' &&
for i in \$(seq 1 30); do
if [ -f /license/valid.flag ]; then break; fi;
sleep 10;
done;
[ -f /license/valid.flag ] || exit 1;
python /opt/CosyVoice/start.py # 假设你的 CosyVoice 启动命令
"
depends_on:
- license-guard
networks:
- flower-network
# ========== whisper-service ==========
whisper-service:
image: registry.cn-hangzhou.aliyuncs.com/flower_shine_raod/flower_shine_raod_repository:whisper-v1.0
container_name: whisper-service
restart: unless-stopped
ports:
- "8004:8004"
environment:
WHISPER_MODEL_ROOT: ./data/models/whisper
WHISPER_WORK_DIR: ./data/work/whisper
WHISPER_DEVICE: cpu
WHISPER_COMPUTE_TYPE: int8
WHISPER_MODEL: medium
WHISPER_CPU_THREADS: 4
WHISPER_NUM_WORKERS: 1
WHISPER_MAX_CONCURRENT_TASKS: 1
volumes:
- ./data/models/whisper:/data/models/whisper:ro
- ./data/work/whisper:/data/work/whisper
- license_data:/license:ro # 🆕 共享卷
deploy:
resources:
limits:
cpus: "4.0"
memory: 8G
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
# 🆕 启动前等待许可证
command: >
sh -c "
echo '等待许可证校验...';
for i in \$(seq 1 30); do
if [ -f /license/valid.flag ]; then break; fi;
sleep 10;
done;
[ -f /license/valid.flag ] || exit 1;
python app.py # 请替换为你的 Whisper 服务启动命令
"
depends_on:
- license-guard
networks:
- flower-network
volumes:
license_data: # 共享卷声明
networks:
flower-network:
driver: bridge
注意:
- CosyVoice 和 whisper 的启动命令请替换成你镜像 CMD 的实际命令(比如 python main.py 等)。
- 若原 Dockerfile 里已有 CMD,这里用 command 覆盖会丢失原 CMD,所以必须把完整启动命令写出来。
- 上面写的 depends_on: license-guard 保证了 license-guard 先启动,但因为它要做校验并写文件,我们又在其他容器的 command 里加了等待循环,双重保险。
3.4 客户使用
- 获取镜像包:购买后,给阿里云 ACR 的拉取命令(用你自己的账号或临时令牌),他一次性拉取全部 5 个镜像。
- 提供拿到 .env 文件模板(作为交付物的一部分直接提供给用户),用户购买后拿到 License Key,填入 .env 文件:
bash
LICENSE_KEY=your-key-from-flower-ai
- 启动,启动容器时,license-guard 会调用你的 API 在线验证 Key,通过后所有服务才能运行。:
bash
docker compose -f docker-compose.prod.yml up -d
- 若 LICENSE_KEY 正确,license-guard 会写标记文件,其他容器依次启动;若 Key 无效,license-guard 退出,其他容器等待超时后自动退出,服务整体不可用。
所有镜像都在同一个私有仓库,授权逻辑集中在 license-guard,Key 可以随时更换(更换后需重启 license-guard 容器即可)。
重新构建并推送 license-guard-v1.0(或新标签 license-guard-v2.0)。你需要实现的线上 API 端点(xxxx.com/license/check):
接收 POST 请求,Body JSON: {"license_key": "xxx"}
在你的数据库里校验 Key 是否存在、未过期、未超过设备数等
返回 {"valid": true} 或 {"valid": false, "message": "原因"}
3.5 一次性拉取全部 5 个镜像的命令
方案 A:提供 pull-all.sh (Linux/macOS) / pull-all.ps1 (Windows)
bash
#!/bin/bash
REGISTRY="registry.cn-hangzhou.aliyuncs.com/flower_shine_raod/flower_shine_raod_repository"
IMAGES=(
"license-guard-v1.0"
"ollama-deepseek-v1.0"
"comfy-lipsync-v1.0"
"cosyvoice-v1.0"
"whisper-v1.0"
)
for tag in "${IMAGES[@]}"; do
echo "正在拉取 $REGISTRY:$tag ..."
docker pull "$REGISTRY:$tag"
done
echo "全部拉取完成!"
方案 B :docker compose pull
如果用户已经有了你提供的 docker-compose.prod.yml,且文件中的镜像地址都已写死,他们可以直接在 compose 文件目录下执行:
bash
docker compose -f docker-compose.prod.yml pull
这会一次性拉取 compose 文件中定义的所有镜像。
3.6 4. 完整交付物清单(用户购买后得到什么)
| 文件/信息 | 说明 |
|---|---|
| 阿里云 ACR 拉取凭证 | 你为分发单独创建的一个 RAM 子账号(仅 ReadOnly 权限),给用户提供 docker login 命令和密码,或使用容器镜像服务的临时令牌。 |
| docker-compose.prod.yml | 包含 5 个服务的编排文件,其中的镜像地址指向你的 ACR,depends_on 和启动等待脚本已写好。 |
| .env 模板 | 内容为 LICENSE_KEY=,用户填入购买时获得的 Key。 |
| License Key | 你从自己的后台生成的唯一 Key,通过邮件/站内信方式交给用户。 |
交付流程:
- 用户在你的个人网站(或通过其他方式)下单付款。
- 你在后台生成一个 License Key,并记录到数据库。
- 你将上述 4 样东西打包为一个 zip 或提供下载链接。
- 用户解压后:
- 先执行 docker login ... 登录你的 ACR;
- 编辑 .env,填入 License Key;
- 运行 docker compose -f docker-compose.prod.yml up -d。
- 系统启动,license-guard 在线验证 Key,验证通过后所有 AI 服务正常运行