抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

最近折腾了一套把 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 管理、数据库持久化和访问密钥轮换。

评论




站点访问量 Loading… 站点访客数 Loading…