
1. 为什么要把 NEW API 和 CLI Proxy API 串成一条通道如果你手里同时跑着 NEW API 和 CLI Proxy API下面简称 CPA大概率会遇到一个很现实的问题两个服务各自有各自的 Key、各自的端口、各自的鉴权文件客户端要接哪个、Key 放哪、额度怎么算全靠脑子记。我试过最原始的做法——NEW API 开 3000 端口对外CPA 开 8317 端口对外结果就是两套密钥满天飞日志分散在两个容器里排查一次请求要来回切三次终端。这篇要解决的就是这件事用 Docker Compose 把 NEW API、CPA、Redis、Postgres、Nginx 编排在同一个 bridge 网络里让 NEW API 作为唯一的对外入口和 Key 通道CPA 退到内网只做认证文件管理和上游渠道Nginx 负责 80/443 的 SSL 终结Cloudflare 负责 DNS 解析和证书签发。最终你只需要记住一个域名、一个 Key就能把请求打到整条链路上。适合谁看已经在云服务器上跑过 Docker、手里有 NEW API 或 CPA 镜像、想把它俩合并成一个统一入口的人。如果你还没装 Docker先补一下docker --version和docker compose version这两条命令能正常输出再往下走。整篇的验证动作都用 curl 完成不需要额外装 Postman。核心检索词先摆出来NEW API 是渠道管理和 Key 分发的面板CLI Proxy API 是管理认证文件并向上游转发请求的代理层Docker 负责编排Nginx 做反向代理和 SSLCloudflare 做 DNS 和证书。这五样东西各司其职缺一个链路就不完整。2. 前置准备目录结构、TaoToken 通道与镜像版本2.1 先把目录骨架搭出来所有配置都放在/opt/proxy下这样迁移和备份都方便。执行下面这条命令一次性把 CPA、NEW API、Nginx 需要的目录和空文件建好mkdir -p /opt/proxy/{cpa/{logs,auths},newapi/{data,logs},nginx/{ssl,logs,conf.d}} \ cd /opt/proxy \ touch docker-compose.yml cpa/config.yaml nginx/nginx.conf nginx/conf.d/api.conf建完之后目录树长这样后面每一步往哪个文件里写内容对照这棵树就不会乱/opt/proxy/ ├── docker-compose.yml ├── cpa/ │ ├── config.yaml │ ├── auths/ │ └── logs/ ├── newapi/ │ ├── data/ │ └── logs/ └── nginx/ ├── nginx.conf ├── conf.d/ │ └── api.conf ├── ssl/ └── logs/2.2 TaoToken 统一 Key 通道的定位整条链路里TaoToken 扮演的是统一 Key 和 API 通道的角色。你可以在 NEW API 的渠道管理里把 CPA 配成一个上游渠道也可以把 TaoToken 的 API 地址作为渠道填进去让 NEW API 统一对外发 Key、统一计费。TaoToken 的 API 入口是https://taotoken.net/api控制台和 Key 管理在https://taotoken.net/console接入文档在https://taotoken.net/doc。这三个地址后面配渠道和验证时会反复用到。需要提前拿到的东西一个 TaoToken 的 API Key在控制台里创建以及 CPA 的config.yaml里要填的api-keys。这两个 Key 不要用同一个CPA 的 Key 是给 NEW API 当上游凭证用的TaoToken 的 Key 是给客户端调 NEW API 用的分开管理出问题好定位。2.3 版本参考镜像版本不用锁死用 latest 即可但心里要有数。NEW API 用calciumion/new-api:latestCPA 用eceasy/cli-proxy-api:latestNginx 用nginx:alpinePostgres 用postgres:15Redis 用redis:latest。云服务器 2C2G 起步就够跑这套内存吃紧的话把 Postgres 换成 MySQL 或者直接上 SQLite 也行但本文按 Postgres 写。3. 可复制配置config.yaml、docker-compose.yml 与 Nginx3.1 CPA 的 config.yaml这个文件控制 CPA 的监听端口、管理密钥、认证目录和重试策略。带的参数必须改带的可以按需改其余照抄host: port: 8317 tls: enable: false remote-management: allow-remote: true secret-key: $2a$10$5dHykttqHWAU.WcYFg0qgOXhDoxC1P7wpZ7i2T8Kj9xqwvVRfTUm2 # 改成你自己的 disable-control-panel: false auth-dir: ~/.cli-proxy-api api-keys: - sk-8SGCShq021BAFgpBE # 改成你自己的 debug: true logging-to-file: true logs-max-total-size-mb: 100 request-retry: 3 max-retry-interval: 30 routing: strategy: round-robin ws-auth: false usage-statistics-enabled: false # 走 NEW API 统计时保持 falseauth-dir里的~在 Linux 容器里就是/root所以 docker-compose 里要把./cpa/auths挂到/root/.cli-proxy-api认证文件放进去就能被 CPA 读到。secret-key是管理接口的密钥所有/v0/management路由都要带它留空则整个管理 API 返回 404。3.2 docker-compose.yml 完整骨架这份 compose 把五个服务放在同一个newapi-network里NEW API 不再直接暴露 3000 端口只通过 Nginx 对外。带 的密码和域名记得改services: new-api: image: calciumion/new-api:latest container_name: new-api restart: always command: --log-dir /app/logs ports: - 3000 # 只在内网暴露不映射到宿主机 volumes: - ./newapi/data:/data - ./newapi/logs:/app/logs environment: - SQL_DSNpostgresql://root:123456postgres:5432/new-api # 改密码 - REDIS_CONN_STRINGredis://redis - TZAsia/Shanghai - ERROR_LOG_ENABLEDtrue - BATCH_UPDATE_ENABLEDtrue depends_on: - redis - postgres - cpa healthcheck: test: [CMD-SHELL, wget -q -O - http://localhost:3000/api/status | grep -o \success\:\\s*true || exit 1] interval: 30s timeout: 10s retries: 3 networks: - newapi-network redis: image: redis:latest container_name: redis restart: always networks: - newapi-network postgres: image: postgres:15 container_name: postgres restart: always environment: - POSTGRES_USERroot - POSTGRES_PASSWORD123456 # 改密码 - POSTGRES_DBnew-api volumes: - pg_data:/var/lib/postgresql/data networks: - newapi-network cpa: image: eceasy/cli-proxy-api:latest container_name: cpa volumes: - ./cpa/config.yaml:/CLIProxyAPI/config.yaml - ./cpa/auths:/root/.cli-proxy-api - ./cpa/logs:/CLIProxyAPI/logs restart: always networks: - newapi-network nginx: image: nginx:alpine container_name: nginx restart: always ports: - 80:80 - 443:443 volumes: - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro - ./nginx/conf.d:/etc/nginx/conf.d:ro - ./nginx/ssl:/etc/nginx/ssl:ro - ./nginx/logs:/var/log/nginx depends_on: - new-api networks: - newapi-network volumes: pg_data: networks: newapi-network: driver: bridge注意new-api的ports写的是3000而不是3000:3000前者只在内网暴露后者会映射到宿主机。既然走 Nginx 反代就没必要让 3000 直接对外。3.3 Nginx 主配置与站点配置nginx/nginx.conf直接照抄不用改user nginx; worker_processes auto; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; use epoll; multi_accept on; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main $remote_addr - $remote_user [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent $http_x_forwarded_for; access_log /var/log/nginx/access.log main; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; client_max_body_size 100m; gzip on; gzip_vary on; gzip_min_length 1024; gzip_comp_level 6; gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xmlrss application/rssxml font/truetype font/opentype application/vnd.ms-fontobject image/svgxml; include /etc/nginx/conf.d/*.conf; }nginx/conf.d/api.conf里把server_name和证书路径改成你自己的域名server { listen 80; server_name api.example.com; # 改成你的域名 return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name api.example.com; # 改成你的域名 ssl_certificate /etc/nginx/ssl/api.pem; ssl_certificate_key /etc/nginx/ssl/api.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; add_header Strict-Transport-Security max-age63072000 always; add_header X-Frame-Options SAMEORIGIN always; add_header X-Content-Type-Options nosniff always; add_header X-XSS-Protection 1; modeblock always; access_log /var/log/nginx/api-access.log main; error_log /var/log/nginx/api-error.log; client_max_body_size 100m; location / { proxy_pass http://new-api:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Port $server_port; proxy_connect_timeout 300s; proxy_send_timeout 300s; proxy_read_timeout 300s; proxy_buffering off; proxy_cache off; proxy_cache_bypass $http_upgrade; } location /nginx-health { access_log off; return 200 healthy\n; add_header Content-Type text/plain; } }proxy_buffering off这一行很关键流式输出SSE如果开着缓冲客户端会看到内容一段一段卡着出来关掉之后才是逐字返回。3.4 Cloudflare 证书与 DNS在 Cloudflare 控制台进入 SSL/TLS 的 Origin Server 页面创建一个 Origin Certificate填你的二级域名比如api.example.com把生成的证书内容存成nginx/ssl/api.pem私钥存成nginx/ssl/api.key然后收紧权限chmod 600 /opt/proxy/nginx/ssl/api.keyDNS 那边加一条 A 记录名称填api内容填你服务器的公网 IP代理状态打开橙色云朵。这样外部访问走 Cloudflare 边缘回源走你签发的 Origin 证书Nginx 只认 Cloudflare 的流量。如果你不想用 Cloudflare 代理把云朵关掉、DNS 直接解析到 IP 也行但那样 Nginx 和 SSL 就白配了不如直接用IP:3000。4. 启动与验证curl 打通每一段链路4.1 启动编排在/opt/proxy下执行docker compose up -d正常输出会看到五个容器依次 Started。如果之前起过又改了配置先docker compose down再 up。启动后确认状态docker compose ps五个服务都应该是running或healthy。NEW API 的 healthcheck 要等 30 秒左右才会变 healthy别急着下结论。4.2 验证 NEW API 内网可达从 Nginx 容器里打 NEW API 的 status 接口确认内网链路通docker exec nginx wget -q -O - http://new-api:3000/api/status返回里应该能看到success:true。这一步不通后面域名访问一定不通先查docker compose logs new-api看数据库连上没有。4.3 验证 CPA 内网可达CPA 的端口是 8317从 NEW API 容器里打它的根路径docker exec new-api wget -q -O - http://cpa:8317/ | head -c 200能返回内容就说明 CPA 起来了。如果返回连接拒绝检查cpa/config.yaml的port是不是 8317以及挂载路径有没有写错。4.4 验证域名与 SSL等 DNS 生效一般 1 到 10 分钟在本地执行curl -I https://api.example.com/nginx-health期望看到HTTP/2 200和healthy。如果卡住或报证书错误先curl -I http://api.example.com看 80 端口有没有 301 跳转再检查 Cloudflare 的 SSL 模式是不是 Full (strict)Flexible 模式会导致回源用 HTTP和你的 443 配置对不上。4.5 验证统一 Key 通道在 NEW API 面板里登录管理员账号进入渠道管理新增一个渠道类型选 OpenAI 兼容API 地址填http://cpa:8317密钥填config.yaml里api-keys那个值。保存后点测试通过就说明 NEW API 能通过 CPA 打到上游。然后用你在 NEW API 里生成的客户端 Key从本地打一次对话接口curl https://api.example.com/v1/chat/completions \ -H Authorization: Bearer sk-你的NEWAPI客户端Key \ -H Content-Type: application/json \ -d {model:gpt-4o-mini,messages:[{role:user,content:ping}]}返回里有choices字段就说明整条链路通了客户端 → Cloudflare → Nginx → NEW API → CPA → 上游。如果返回 401检查 Key 是不是 NEW API 里生成的返回 502检查 Nginx 的proxy_pass指向的服务名对不对。5. 本篇常见错排查5.1 docker compose 重启报 iptables 错误docker compose down之后再 up有时会撞上Failed to Setup IP tables: Unable to enable SKIP DNAT rule。这是 Docker 的 nat 链没清干净重启 Docker 服务即可systemctl restart docker docker compose up -d5.2 防火墙放了端口但外部还是不通CentOS 上firewall-cmd --list-ports显示 443 已放行但外部访问超时多半是云服务器安全组没开。安全组和系统防火墙是两层都要放行。另外注意firewall-cmd的提示连接实际走的是dockerzone不是public但外部入站走 public所以 80/443 在 public 放行就够了。5.3 Nginx 启动后 502 Bad Gateway九成是proxy_pass里的服务名写错或者 NEW API 还没 healthy。先docker compose logs nginx看错误日志再docker exec nginx wget -q -O - http://new-api:3000/api/status确认后端活着。服务名必须和 compose 里的container_name或服务名一致本文用的是new-api。5.4 流式输出卡顿或一次性返回检查api.conf里proxy_buffering off和proxy_cache off有没有漏。另外proxy_read_timeout建议设到 300s默认 60s 在长回答场景下会提前断开。5.5 CPA 管理接口返回 404config.yaml里secret-key留空会导致所有/v0/management路由返回 404。填上密钥后重启 CPA 容器请求时带上Authorization: Bearer secret-key即可。6. 后续接入与 Key 管理链路跑通之后日常操作基本都在 NEW API 面板里完成发 Key、看用量、调渠道权重。如果你要长期跑编码类或 Agent 类任务建议把客户端直接指向 NEW API 的域名Key 用 NEW API 生成的这样额度统计和限流都在一个地方管。TaoToken 的 Coding Plan 适合这种长期编码场景可以在https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite看具体方案。需要新建 Key 或者查用量的时候控制台在https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewriteAPI Key 管理页在https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite。接入细节和参数说明看文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite模型对话调试用https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite。API 入口统一是https://taotoken.net/api配置渠道时直接填这个地址就行。最后提醒一句config.yaml里的secret-key和api-keys、compose 里的数据库密码、Cloudflare 的私钥这三类东西别提交到 Git也别贴到公开渠道。整套配置备份的时候把ssl/api.key和cpa/auths单独加密存。