最近折腾了一套把 CLIProxyAPI 和 CPA-Manager 部署到 Hugging Face Space 的方案。目标很明确:不买 VPS,尽量使用免费资源,把 API 网关、管理面板和用量统计放在同一个 Space 里运行,并且通过同一个域名访问。
最终访问结构如下:
1 2 3 4 5
| CPA 主服务: https://username-space-name.hf.space/
CPA-Manager 管理面板: https://username-space-name.hf.space/cpm/
|
这篇文章记录完整部署思路、关键配置和踩坑点。
为什么不能直接照搬 VPS 方案
在 VPS 上,常见做法是:
1 2
| Docker Compose 跑 cli-proxy-api 和 cpa-manager 宿主机 Caddy 负责 HTTPS 和反代
|
但是 Hugging Face Docker Space 的模型不一样。Space 对公网通常只暴露一个应用端口,默认是 7860。也就是说,不能像 VPS 一样直接把多个容器、多个端口都暴露出去。
所以 Space 里的方案要改成:
1 2 3 4 5 6 7 8 9 10 11 12 13
| 一个 Docker 容器 容器内同时启动: - CLIProxyAPI - CPA-Manager - Caddy
公网只暴露: - 7860
内部端口: - 8317 -> CLIProxyAPI - 18317 -> CPA-Manager - 7860 -> Caddy 反代入口
|
路由关系如下:
1 2 3 4 5 6 7 8 9 10
| / -> CLIProxyAPI /v1/* -> CLIProxyAPI /v0/management/usage-queue -> CLIProxyAPI /cpm/ -> CPA-Manager /health -> CPA-Manager /status -> CPA-Manager /setup -> CPA-Manager /usage-service/* -> CPA-Manager /v0/management/usage -> CPA-Manager /v0/management/model-prices* -> CPA-Manager
|
这里最重要的是不要把 /v0/management/usage* 全部转给 CPA-Manager,否则会误拦截 CPA 自己的 /v0/management/usage-queue。
新建 Hugging Face Docker Space
进入:
1
| https://huggingface.co/new-space
|
建议配置:
1 2 3
| Space SDK: Docker Visibility: Public Hardware: CPU Basic
|
Space 仓库里只需要放这些文件:
1 2 3 4 5
| Dockerfile Caddyfile config.yaml start.sh README.md
|
Dockerfile
这里使用 eceasy/cli-proxy-api:latest 作为基础镜像,然后在构建阶段下载 CPA-Manager 的 Linux 二进制文件,并安装 Caddy 做内部反代。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| FROM eceasy/cli-proxy-api:latest
USER root
ARG TARGETARCH=amd64
RUN apk add --no-cache bash ca-certificates caddy curl gcompat jq libc6-compat tar
WORKDIR /app
RUN cp /CLIProxyAPI/CLIProxyAPI /app/cli-proxy-api \ && chmod +x /app/cli-proxy-api
RUN set -eux; \ case "${TARGETARCH}" in \ amd64|x86_64) asset_arch="amd64" ;; \ arm64|aarch64) asset_arch="arm64" ;; \ *) echo "Unsupported TARGETARCH=${TARGETARCH}" >&2; exit 1 ;; \ esac; \ asset_url="$(curl -fsSL https://api.github.com/repos/seakee/CPA-Manager/releases/latest \ | jq -r --arg arch "${asset_arch}" '.assets[] | select(.name | test("linux_" + $arch + "\\.tar\\.gz$")) | .browser_download_url' \ | head -n 1)"; \ test -n "${asset_url}"; \ curl -fsSL "${asset_url}" -o /tmp/cpa-manager.tar.gz; \ mkdir -p /tmp/cpa-manager-extract; \ tar -xzf /tmp/cpa-manager.tar.gz -C /tmp/cpa-manager-extract; \ find /tmp/cpa-manager-extract -type f -name cpa-manager -exec cp {} /app/cpa-manager \; -quit; \ chmod +x /app/cpa-manager; \ rm -rf /tmp/cpa-manager.tar.gz /tmp/cpa-manager-extract
RUN mkdir -p /tmp/.cli-proxy-api /tmp/logs /tmp/pg_cache/pgstore /data \ && chmod -R 777 /tmp /data
COPY config.yaml /app/config.yaml COPY Caddyfile /app/Caddyfile COPY start.sh /app/start.sh
RUN cp /app/config.yaml /app/config.example.yaml \ && chmod +x /app/start.sh
ENV TZ=Asia/Shanghai ENV PORT=7860 ENV HTTP_ADDR=0.0.0.0:18317 ENV USAGE_DATA_DIR=/data ENV USAGE_COLLECTOR_MODE=auto ENV USAGE_BATCH_SIZE=100 ENV USAGE_POLL_INTERVAL_MS=500 ENV USAGE_QUERY_LIMIT=50000 ENV USAGE_CORS_ORIGINS=* ENV USAGE_RESP_QUEUE=usage ENV USAGE_RESP_POP_SIDE=right
EXPOSE 7860
CMD ["/app/start.sh"]
|
这里有一个关键点:
1
| RUN cp /app/config.yaml /app/config.example.yaml
|
CLIProxyAPI 启动时会读取这个模板文件,缺少它可能会报:
1
| open /app/config.example.yaml: no such file or directory
|
config.yaml
CPA 不要直接监听 7860,否则会和 Caddy 冲突。这里让 CPA 监听内部端口 8317。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| host: "0.0.0.0" port: 8317
pgstore-dsn: "${PGSTORE_DSN}"
auth-dir: "/tmp/.cli-proxy-api" logging-to-file: true logs-dir: "/tmp/logs"
remote-management: enabled: true secret-key: "${MANAGEMENT_PASSWORD}"
usage-statistics-enabled: true redis-usage-queue-retention-seconds: 600
commercial-mode: true debug: false
|
如果需要持久化配置,推荐使用外部 PostgreSQL,例如 Aiven、Supabase 等。连接串放到 Space Secret 里,不要写进仓库。
Caddyfile
Caddy 监听 7860,负责把外部请求分发给内部的 CPA 和 CPA-Manager。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| :7860 { encode gzip zstd
redir /cpm /cpm/
handle /cpm/ { rewrite * /management.html reverse_proxy 127.0.0.1:18317 { header_up Host {host} header_up X-Real-IP {remote_host} header_up X-Forwarded-For {remote_host} header_up X-Forwarded-Proto {scheme} } }
handle_path /cpm/* { reverse_proxy 127.0.0.1:18317 { header_up Host {host} header_up X-Real-IP {remote_host} header_up X-Forwarded-For {remote_host} header_up X-Forwarded-Proto {scheme} } }
handle /health { reverse_proxy 127.0.0.1:18317 }
handle /status { reverse_proxy 127.0.0.1:18317 }
handle /setup { reverse_proxy 127.0.0.1:18317 }
handle /usage-service/* { reverse_proxy 127.0.0.1:18317 }
@cpam_usage { path /v0/management/usage path /v0/management/usage/export path /v0/management/usage/import } handle @cpam_usage { reverse_proxy 127.0.0.1:18317 }
handle /v0/management/model-prices* { reverse_proxy 127.0.0.1:18317 }
handle { reverse_proxy 127.0.0.1:8317 { header_up Host {host} header_up X-Real-IP {remote_host} header_up X-Forwarded-For {remote_host} header_up X-Forwarded-Proto {scheme} } } }
|
/cpm/ 单独 rewrite 到 /management.html 是为了避免 CPA-Manager 自己重定向到根路径后,被 Caddy 转发给 CPA,导致页面 404。
start.sh
容器里需要同时启动三个进程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| #!/usr/bin/env bash set -euo pipefail
export CPA_UPSTREAM_URL="${CPA_UPSTREAM_URL:-http://127.0.0.1:8317}" export CPA_MANAGEMENT_KEY="${CPA_MANAGEMENT_KEY:-${MANAGEMENT_PASSWORD:-}}" export HTTP_ADDR="${HTTP_ADDR:-0.0.0.0:18317}" export USAGE_DATA_DIR="${USAGE_DATA_DIR:-/data}" export USAGE_COLLECTOR_MODE="${USAGE_COLLECTOR_MODE:-auto}"
if [[ -z "${PGSTORE_DSN:-}" ]]; then echo "PGSTORE_DSN is not set. Add it in Space Settings -> Variables and secrets." >&2 fi
if [[ -z "${MANAGEMENT_PASSWORD:-}" ]]; then echo "MANAGEMENT_PASSWORD is not set. Add it in Space Settings -> Variables and secrets." >&2 fi
/app/cli-proxy-api --config /app/config.yaml & cpa_pid=$!
/app/cpa-manager & cpam_pid=$!
caddy run --config /app/Caddyfile --adapter caddyfile & caddy_pid=$!
cleanup() { kill "${cpa_pid}" "${cpam_pid}" "${caddy_pid}" 2>/dev/null || true } trap cleanup EXIT INT TERM
wait -n "${cpa_pid}" "${cpam_pid}" "${caddy_pid}" exit_code=$? cleanup wait || true exit "${exit_code}"
|
Space 环境变量
在 Space 的 Settings -> Variables and secrets 中配置:
1 2 3 4 5 6
| PGSTORE_DSN Secret MANAGEMENT_PASSWORD Secret MANAGEMENT_STATIC_PATH /tmp PGSTORE_LOCAL_PATH /tmp/pg_cache TZ Asia/Shanghai PORT 7860
|
PostgreSQL 连接串示例:
1
| postgres://username:password@host:port/defaultdb?sslmode=require
|
注意不要把真实连接串提交到 GitHub,也不要写进 README 或博客。
启动验证
Space 构建完成后,可以访问:
1
| https://username-space-name.hf.space/health
|
如果 CPA-Manager 正常,会返回:
1
| {"ok":true,"service":"cpa-manager"}
|
管理面板访问:
1
| https://username-space-name.hf.space/cpm/
|
CPA OpenAI-compatible 接口访问:
1
| https://username-space-name.hf.space/v1
|
Codex 接入 CPA Space
如果想让 Codex 默认走这个 Space,可以在 ~/.codex/config.toml 里添加自定义 provider:
1 2 3 4 5 6 7 8 9 10 11 12
| model = "gpt-5.5" model_provider = "cpa_space"
[model_providers.cpa_space] name = "CPA Space" base_url = "https://username-space-name.hf.space/v1" wire_api = "responses" env_key = "CPA_SPACE_API_KEY"
[profiles.cpa_space] model_provider = "cpa_space" model = "gpt-5.5"
|
然后设置环境变量:
1 2
| [Environment]::SetEnvironmentVariable("CPA_SPACE_API_KEY", "your-cpa-api-key", "User") $env:CPA_SPACE_API_KEY="your-cpa-api-key"
|
测试:
1
| codex exec --ephemeral --skip-git-repo-check "Reply with exactly: OK"
|
如果返回 OK,说明 Codex 已经走到了你的 CPA Space。
常见问题
Space 一直 Building 或 Starting
先看 Logs。只要日志里已经看到服务启动成功,可以直接访问 Space 域名测试。有时页面状态显示会滞后。
/cpm/ 打开后 404
通常是 CPA-Manager 重定向到了 /management.html,但 Caddy 把这个路径转给了 CPA。解决方式是给 /cpm/ 单独 rewrite:
1 2 3 4
| handle /cpm/ { rewrite * /management.html reverse_proxy 127.0.0.1:18317 }
|
API Key 或认证文件不见了
CPA 的数据库配置和认证文件不是一回事。数据库可以保存配置和用量,认证文件通常走 auth-dir。如果 auth-dir 放在 /tmp,Space 重启后认证文件可能丢失。
如果要长期稳定,建议:
1 2
| 配置和用量 -> PostgreSQL 认证文件 -> 重新上传,或改造为持久化路径
|
Codex 提示 Missing environment variable
说明当前 Codex 进程没有读到环境变量。设置后需要重开终端或重启 Codex:
1
| [Environment]::SetEnvironmentVariable("CPA_SPACE_API_KEY", "your-cpa-api-key", "User")
|
Codex 提示 Approaching rate limits
这个提示通常和当前模型、上游账号额度、共享 API Key 的整体使用量有关,不代表一句话就把额度用完了。如果默认使用高消耗模型,比如 gpt-5.5,Codex 会更早提示切换到 mini。
总结
这套方案的核心是:Hugging Face Space 只暴露一个公网端口,所以要在单容器里跑多进程,再用 Caddy 做内部路由。
最终结构是:
1 2 3 4 5 6 7
| Hugging Face HTTPS | v Space :7860 Caddy | +-- / -> CLIProxyAPI :8317 +-- /cpm/ -> CPA-Manager :18317
|
优点是部署成本低,域名统一,管理面板和 API 网关都在一个 Space 里。缺点是免费 Space 的文件系统不适合保存重要认证文件,生产使用时要特别注意 Secret 管理、数据库持久化和访问密钥轮换。