配套開源倉庫:本文的一鍵部署版本,一條命令完成 Docker 部署、key 生成與設定渲染,另附客戶端設定產生腳本與 Nginx 範例:

kuangyongjie/cliproxyapi-codex-deploy

git clone https://github.com/kuangyongjie/cliproxyapi-codex-deploy.git
cd cliproxyapi-codex-deploy
./scripts/setup.sh

如果你現在已經把 Codex 用順手了,遲早會碰到一個挺現實的問題:我到底要不要繼續老老實實用官方直登,還是乾脆自己加一層代理,把 Codex 放進我自己的 AI 開發環境裡。

這件事一開始看起來像“技術潔癖”,但真用起來就不是了。你如果只有一台電腦、一個帳號,而且平時主要就是自己本地開工,那官方直接登入確實最輕,沒什麼好爭的。裝完能用,少一層服務,少一層維護,也少一層排障路徑。這種場景下,為了“可控”強行多架一個代理,很多時候只是給自己加活。

但一旦你開始有別的想法,情況就變了。比如你不想每臺機器都重新登一遍;比如你希望 Codex、Claude Code、Gemini CLI 這些東西后面都能走同一個入口;再比如你想把 OAuth 登入狀態、存取控制、相容介面、甚至後面的多帳號輪詢都收進一台你自己能控的服務裡。到了這個階段,像 CLIProxyAPI 這種專案就很有意思,因為它不是單純“幫你轉發請求”,而是在做一層可控的接入面。

我當時真開始認真看這條路,也不是因為“想折騰得更高階”,而是因為發現官方直登很適合單機開工,但一旦我想把 Codex 接進自己平時那套 VPS、自部署工具和腳本環境裡,很多東西就開始變散了。登入狀態在本機,別的專案各配各的入口,後面再加一個呼叫端,又是一輪重複設定。核心問題其實很簡單:如果你想把 Codex 透過 CLIProxyAPI 反代出去,用 Docker 跑在一台 Linux 伺服器上,跑通 OpenAI OAuth,然後回到本地 Codex App 改設定接過去,這條鏈路到底應該怎麼定,哪裡最容易翻車。

代理方案到底好在哪

我先把結論說清楚:如果你只圖最快開工,官方直登還是最低摩擦方案;但如果你要的是“以後我的 Codex 不只是一個 app,而是我整套開發環境的一部分”,那代理方案就很值得。它最大的價值,不是炫,而是把“某臺機器上的登入狀態”變成“我自己掌控的一層基礎設施”。

而且這個“基礎設施”一旦搭出來,受益的通常不只是 Codex 自己。你很快就會發現,真正方便的地方在於你終於有了一個可以複用的 OpenAI-compatible 入口。以後你自己部署的其他專案,只要支援自定義 base_url 或 OpenAI 相容介面,理論上都能直接接過來。像龍蝦這類專案,其實就很適合吃這種統一入口。它本身作為前端或呼叫層,並不值得你再單獨維護一套上游登入狀態;讓它和 Codex 一樣都掛在這層代理後面,實際會省心很多。別的自部署前端、Agent 面板、腳本服務也一樣,它們只要認你這層代理發出去的 api-key,就能共用同一個入口。

這裡最容易被低估的一點,是“共享”的其實不是某個寫死在專案裡的上游金鑰,而是代理層儲存下來的那份 OAuth / token 狀態。以前你可能要在 Codex、腳本、面板、另一個容器裡各自處理登入,機器一換、容器一重建,又得重新補一遍。現在變成上游狀態集中儲存在 CLIProxyAPI 這一層,下游專案只認代理地址和代理 key。這個差別看起來像少填幾項設定,實際上是把整套維護方式改了。你後來再接一個龍蝦專案,或者再補一個只會讀 OpenAI-compatible 介面的內部工具,腦子裡想的就不是“這次又要怎麼登入一次”,而是“給它發哪把 key、掛到哪個入口”。

這件事對獨立開發者特別有價值,因為它把“每個專案各配一遍”變成了“上游只配一次,下游按專案分發自己的存取 key”。換句話說,真正敏感、真正麻煩的那層 OAuth 狀態和 token,不再散落在每臺機器、每個容器、每個專案設定裡,而是被你集中收口到了代理服務這一層。以後你換機器、重灌客戶端、補一個新專案,改的通常只是下游存取方式,不是再從頭重做上游授權。

而且這裡還有個很實際的好處:代理 key 的策略可以按你的階段來。你現在專案不多,完全可以先讓幾個自己控制的內部專案共用一把代理 key,先把鏈路打通,省掉重複設定。等後面 Codex、龍蝦專案、聊天前端、自動化腳本這些東西越掛越多,再按專案拆 key、做限流、做日誌區分。也就是說,這層代理既適合你現在先圖省事,也給你後面規範化留了位置。

如果你後面還會繼續接別的工具,這種好處會越來越明顯。比如今天你先讓 Codex 走這層代理,明天你再把 Claude Code、Gemini CLI,或者你自己部署的別的呼叫端也放進來,外部看到的依舊可以是一個統一入口。甚至從維運視角看,這也很省腦子:限流、存取控制、日誌、上游切換、模型對映,都開始有了一個固定落點,而不是分散在每個專案自己的環境變數裡。

先把鏈路拆成三塊

我最後理解這條鏈路時,是把它硬拆成了三塊。第一塊是伺服器上跑起來的 CLIProxyAPI 服務本身。第二塊是 Codex 的 OAuth 登入狀態,要真正寫進這個服務掛載出來的認證目錄裡。第三塊才是本地 Codex 客戶端設定,也就是 ~/.codex/config.toml 和 ~/.codex/auth.json 這兩個檔案。只要你腦子裡這三層不混,後面其實就順了;反過來,很多人第一次接不通,往往就是把“代理登入”和“客戶端設定”當成了一件事。

CLIProxyAPI 官方 README 現在已經明確寫了它支援 Codex via OAuth,也給了 Docker、Docker Compose、Codex 客戶端設定檔案。光看官方檔案,其實路線並不複雜。真正麻煩的是第一次做時,你很容易誤判每個檔案到底是給誰用的。config.yaml 是代理服務的,auth/ 是代理登入狀態的,~/.codex/config.toml 是本地 Codex App 的,這三份東西別攪在一起。

伺服器端:Docker 部署與 OAuth 登入

如果是我自己搭,我會先在伺服器上收一個很小的目錄,不去搞太花的結構。差不多這樣就夠了:

/opt/cliproxyapi/
├── docker-compose.yml
├── config.yaml
└── auth/

這裡的 auth/ 很關鍵,因為官方 auth-dir 預設就是 ~/.cli-proxy-api。你用 Docker 時,本質上就是把容器裡的這個目錄對映出來,讓 OAuth 登入後留下來的狀態別隨著容器消失。很多人第一次能登入成功,但第二次容器一重建就發現狀態沒了,問題基本都出在這裡。

config.yaml 我不建議一上來寫得很大。先讓它穩穩跑起來再說。官方 config.example.yaml 裡,最值得先關心的其實就這麼幾項:

host: ""
port: 8317
auth-dir: "~/.cli-proxy-api"

api-keys:
  - "<proxy-api-key>"

remote-management:
  allow-remote: false
  secret-key: ""

這裡有兩個點最好一開始就想明白。一個是 api-keys 不是 OpenAI 的 key,它是你下游客戶端存取這層代理時要帶的存取令牌。這個設計反而正是代理方案的價值之一,因為你可以給不同專案發不同的下游 key。比如 Codex App 用一個,龍蝦這類自部署專案再用一個,後面想吊銷、輪換或者拆分權限時,動的是代理層,不是每個專案都回頭重配上游。另一個是 remote-management.allow-remote,如果你現在只是拿它服務 Codex,沒有明確的遠端管理需求,就先關著。這個專案預設也是關的,我覺得這個預設值挺對,因為很多人剛搭起來就喜歡順手把控制面也一起暴露,後面反而給自己留坑。

Docker Compose 這塊,我會先定成最保守也最容易解釋的版本。核心目的只有兩個:把 API 埠映出來,把認證目錄和設定檔案掛進去。

services:
  cli-proxy-api:
    image: eceasy/cli-proxy-api:latest
    container_name: cli-proxy-api
    restart: unless-stopped
    ports:
      - "127.0.0.1:8317:8317"
      - "127.0.0.1:1455:1455"
    volumes:
      - ./config.yaml:/CLIProxyAPI/config.yaml
      - ./auth:/root/.cli-proxy-api

這兩個連接埠我都綁在 127.0.0.1 上,是有意的。API 連接埠只需要被同一台主機的 Nginx 存取(後文 Nginx 本來就是反代 http://127.0.0.1:8317),而 OAuth 回呼監聽面曾經有對外發布的歷史風險(上游 issue #3463)。預設把這兩個連接埠暴露到公網,等於把一個臨時的登入面變成永久的攻擊面,所以不要把它當成安全的預設值。如果你的網路拓撲確實需要更廣的存取,再單獨聲明限制條件。

再多說一句映像 tag::latest 是浮動 tag。本文的 v8 欄位對照核對的是上游倉庫 2026-10-14 的 main 分支,並沒有用這個映像實際跑過。部署前先確認你拉到的 tag 還是這個佈局,或者直接鎖定一個自己驗過的 digest。

這裡把 1455 也對映出來,不是因為你的業務流量都走它,而是因為官方檔案明確寫了 Codex 的本地 OAuth 回撥用這個埠。這個細節很容易被忽略,因為很多人眼裡只盯著 API 埠 8317,結果服務明明跑著,--codex-login 也執行了,最後授權跳不回來,或者瀏覽器裡轉完發現代理這邊沒拿到狀態。不是 CLIProxyAPI 壞了,是你把 OAuth 這一段鏈路想得太簡單了。

等 Compose 起好以後,先別急著改本地 Codex。這個順序真的很重要,應該先驗證代理自己能不能拿到 Codex 的 OAuth 狀態。官方給的命令是:

docker compose exec cli-proxy-api /CLIProxyAPI/CLIProxyAPI -no-browser --codex-login

我自己很喜歡這個 -no-browser,尤其是在遠端 Linux 伺服器上。它不會嘗試在容器裡開瀏覽器,而是把登入 URL 直接吐出來,你複製到本地瀏覽器裡做授權就行。這一步你要理解成“給代理服務登入”,不是“給我的 Codex App 登入”。也就是說,真正被授權的是 CLIProxyAPI 這一層,授權成功後,狀態應該會落到你掛載出來的 auth/ 目錄裡。

關於這個回呼,有一件事在遠端機器上特別容易搞錯。瀏覽器跑在你的本地電腦上,所以 http://localhost:1455/auth/callback 解析到的是你的本地電腦,而不是 VPS——不管容器的連接埠對映怎麼寫。對於遠端 Linux/VPS 場景,真正能閉環的順序是:

  1. 先在本地(不是伺服器上)建好隧道:
    ssh -N -L 1455:127.0.0.1:1455 <user>@<vps>
  2. 再執行上面的登入命令。CLI 會輸出 OAuth URL(-no-browser 下它甚至會自己列印 SSH 隧道指引)。
  3. 在本地瀏覽器裡開啟這個 URL 完成授權,localhost:1455 的回呼就會經由隧道回到容器。

如果跳過隧道,CLI 會停在 “Waiting for Codex authentication callback…”,約 15 秒後提示 Paste the Codex callback URL (or press Enter to keep waiting):,這時直接把瀏覽器網址列裡的完整回呼 URL 貼上就行——這是 CLI 自帶的手動路徑,不是野路子。

如果這一段沒跑通,後面你本地怎麼改 config.toml 都沒意義。因為客戶端只是換了入口,真正能不能代表你去拿 Codex 結果,還是得看代理自己有沒有那份 OAuth 狀態。所以第一次做時,我會先看兩個東西:一個是 docker compose logs -f 的即時輸出,另一個是 auth/ 目錄裡有沒有新檔案寫進去。前者告訴你登入流程卡在哪,後者告訴你這次授權到底是不是落盤了。

config.yaml 欄位速查(v8)

前面的範例裡的 config.yaml 用的是舊版頂層欄位寫法(host、port、auth-dir、api-keys、remote-management),照樣能用。但上游已經切到 v8 佈局(config-version: 8),所有設定都收進具名小節裡。如果你對照的是上游倉庫現在的 config.example.yaml,看到的就是新寫法。下面是 Codex 代理場景實際用到的欄位對照,每個欄位名稱都已對照上游 main 分支(2026-10-14 實取)核對過。Plus fork 可能有分化,套用前先確認它們鎖定的映像版本和 config.example.yaml。

欄位(v8)舊寫法說明
config-version: 8—宣告 v8 佈局。新舊寫法同時出現時,v8 優先。
server.hosthost綁定網卡。空字串表示監聽所有網卡。
server.portport客戶端呼叫的 API 連接埠,本文用 8317。
oauth.auth-dirauth-dirCodex OAuth 狀態存放位置,必須和 auth/ 的掛載對應上,否則重啟就丟登入。
access.api-keysapi-keys發給下游客戶端的 key,不是上游 OpenAI 憑證。
management.allow-remoteremote-management.allow-remote沒有明確的遠端管理需求就保持 false。
management.secret-keyremote-management.secret-key管理 API 的金鑰,留空則管理 API 直接停用。
client.codex.optimize-multi-agent-v2—選用,Codex Desktop / codex-tui 多 agent 場景最佳化,預設 false。

按本文設定寫成的 v8 風格最小設定:

config-version: 8

server:
  host: ""
  port: 8317

oauth:
  auth-dir: "~/.cli-proxy-api"

access:
  api-keys:
    - "<proxy-api-key>"

management:
  allow-remote: false
  secret-key: ""

兩點值得單獨說。第一,access.api-keys 和舊寫法的 api-keys 是同一份名單:發給下游的 key,永遠不是上游密鑰。第二,容器裡 oauth.auth-dir 預設解析到 /root/.cli-proxy-api,這正是 compose 裡把 ./auth 掛到那裡的原因。兩邊改了一邊沒改,「昨天還好好的今天重啟就掉登入」的坑就回來了。

Docker Compose 獨立路徑

文首的配套倉庫把下面這些做成了一鍵指令碼。如果你想每個檔案都自己掌控、看清楚每一步在跑什麼,下面是同一套設定的獨立 compose 版本,不依賴任何輔助指令碼。

目錄還是前面那個小結構:

/opt/cliproxyapi/
├── docker-compose.yml
├── config.yaml
└── auth/

compose 檔案就用「伺服器端」上面那一份——同樣的 127.0.0.1 綁定、同樣的掛載,按上游倉庫自己的 docker-compose.yml 裁到本文需要的樣子。這裡不再重複貼第二份,因為正確設定應該只有一份源頭,照著上面複製即可。./auth 還是要落到 oauth.auth-dir 指的地方,否則每次重建容器 OAuth 狀態就丟。

啟動並觀察:

docker compose config
docker compose up -d
docker compose logs -f

第一條值得養成習慣:docker compose config 只渲染生效設定、不啟動任何容器,縮排寫錯、掛載路徑寫錯這類問題在變成執行時謎題之前就能發現。

然後按上一節同樣的方式登入:

docker compose exec cli-proxy-api /CLIProxyAPI/CLIProxyAPI -no-browser --codex-login

說句實話:上面 compose 檔案的每個欄位都對照過上游倉庫的 docker-compose.yml 和本文設定,但這次更新我沒有在真機上實際執行過這份檔案。如果你的環境不一樣(比如 rootless Docker、非預設資料目錄),卷路徑是最先要核對的地方。

本地端:Codex 客戶端與 Nginx 設定

到這裡很多人會有個誤區:既然代理已經做完 OpenAI OAuth 了,那本地 Codex 不是就可以什麼都不用配了嗎?實際上不是。按 CLIProxyAPI 官方 Codex 客戶端檔案,本地還是要改兩個檔案:~/.codex/config.toml 和 ~/.codex/auth.json。只是這裡的邏輯跟你直連 OpenAI 時已經完全不一樣了。

你可以把它理解成兩層 key。上游那層,是代理自己持有的 OAuth/token 狀態,它代表的是“CLIProxyAPI 這層能不能替你去拿 Codex 能力”;下游這層,則是你發給客戶端或別的專案的存取 key,它代表的是“誰可以呼叫我的代理”。這個拆分很值錢,因為以後你再接一個新專案,通常不需要再碰上游授權,只需要給它一把新的下游 key,再把 base_url 指過來就行。

如果你的代理跑在遠端伺服器,我會直接把 config.toml 理解成“把 Codex 的 provider 指到我自己的域名上”。像這樣:

model_provider = "cliproxyapi"
model = "gpt-5-codex"
model_reasoning_effort = "high"

[model_providers.cliproxyapi]
name = "cliproxyapi"
base_url = "https://codex-proxy.example.com/v1"
wire_api = "responses"

這裡最容易寫錯的,其實反而不是模型名,而是兩處小地方。第一處是 base_url,很多人明明服務跑在遠端域名上了,結果還沿用官方示例裡的 http://127.0.0.1:8317/v1,最后本地 Codex 當然還是在找自己機器上的代理。第二處是 wire_api = "responses",這個不能漏。CLIProxyAPI 官方給 Codex 的相容方式就是走 responses,少了這一句,客戶端和代理經常會出現你看不懂的行為偏差。

至於 auth.json,它反而比很多人以為的簡單得多。官方檔案給的例子就是:

{
  "OPENAI_API_KEY": "sk-dummy"
}

第一次看到這裡,很多人會皺眉頭:我都走代理了,怎麼還要填 OPENAI_API_KEY?其實你要把它理解成客戶端的一個佔位要求,而不是“這裡真要放一個能用的 OpenAI key”。真正起作用的是上面的 config.toml provider 設定,這裡的 sk-dummy 只是讓 Codex 本地側的設定結構完整。也正因為這樣,我更不建議在這裡手癢放進真的上游 key,你本來都已經把接入控制收口到代理層了,沒必要又把敏感資訊往客戶端這一層塞回去。

如果你還想把 8317 收到一個正式域名後面,比如 codex-proxy.example.com,那 Nginx 也別搞太複雜。最重要的是保住 /v1/ 這個路徑,不要為了看起來整齊把它改來改去。一個夠用的寫法就是:

server {
  listen 443 ssl http2;
  server_name codex-proxy.example.com;

  location /v1/ {
    proxy_pass http://127.0.0.1:8317/v1/;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
  }
}

只要這層入口穩定下來,它的意義就不只是給 Codex 用。很多自部署專案本身吃的就是 OpenAI-compatible 協議,它們往往也支援改 OPENAI_BASE_URL、OPENAI_API_BASE 或類似變數。到那一步,你其實已經不再是在“給 Codex 做個專用代理”,而是在給自己手上的一批專案搭一個統一 AI 閘道器。你今天先接 Codex,明天再接龍蝦專案或別的前端,設定思路會非常像:

OPENAI_BASE_URL=https://codex-proxy.example.com/v1
OPENAI_API_KEY=<proxy-api-key-for-this-project>

真正舒服的地方就在這裡。上游 OAuth、模型接入、帳號輪詢這些重東西,集中在 CLIProxyAPI 層;下游每個專案只管認一個穩定入口和一把自己的存取 key。以後你想換上游、加帳號、做容災、甚至把某個專案單獨禁掉,都不用把所有客戶端和所有自部署服務重新翻一遍。

如果你手上已經有別的自部署專案,這種統一入口的價值會更直接。比如你拿 Codex 做程式碼工作流程,拿龍蝦這種前端專案做日常對話或模型試驗,再加幾個內部腳本去跑自動化任務。以前這三類東西很容易各自帶一套設定,哪個專案裡到底塞了什麼 key,過兩週自己都記不清。現在它們都掛到同一個代理後面,真正長期儲存的只有代理層那份上游狀態,你要管理的是“哪個專案能不能用、用哪把代理 key、走哪個域名入口”,而不是到處找散落的 token。

最常見的翻車點

如果你問我實際最常見的翻車點是什麼,我會說不是 Docker,也不是 Compose 本身,而是下面這些很細的小地方:

  • 代理服務已經跑起來了,但 auth/ 根本沒拿到 Codex OAuth 狀態
  • 本地 config.toml 改了,可 base_url 還寫著 localhost
  • Nginx 只反代了域名,沒有把 /v1/ 路徑保留下來
  • 忘了 wire_api = "responses"
  • 以為 auth.json 要填真 OpenAI key,又把本來不需要暴露的東西放回本地

你會發現,這些問題都不是“命令寫錯”,而是你對這條鏈路的職責分層有沒有想清楚。直接登入當然簡單,因為認證、請求、客戶端都在一層裡;CLIProxyAPI 這條路則是把登入狀態、代理服務、反代入口、本地客戶端拆開了。拆開以後更靈活,但第一遍接的時候腦子裡一定要有拓撲。

所以最後還是回到那個問題:它跟直接登入到底有什麼區別,值不值得。我的看法挺明確的。直接登入適合“我自己馬上要用”;CLIProxyAPI 更適合“我要把 Codex 變成我整套 AI 開發環境裡可控的一部分”。前者低摩擦,後者高自由度。你如果已經開始同時用多種 AI coding 工具,或者有多臺機器要保持一致入口,那後者的價值會越來越明顯。尤其當你手裡不只 Codex 一個呼叫端,而是還掛著龍蝦這類自部署服務時,這個差別會一下子變得很具體:你終於不用每個專案都重新處理一次上游認證,也不用把金鑰和 token 分散埋在不同容器和設定檔案裡。

還有一件事我覺得很多人會後知後覺:既然你已經開始把這類東西做成自己的基礎設施,就別把 config.yaml 和 auth/ 當成隨時可重來的臨時檔案。它們其實已經是狀態了。真要長期跑,最好順手把這兩塊也納進備份裡。不然哪天容器重建、機器遷移、目錄誤刪,你會發現最費時間的從來不是重拉鏡像,而是把那些登入狀態和細碎設定重新拼回來。

如果你現在就在搭 Codex 工作流程,這篇 新版 Codex 有什麼變化:為什麼它現在更像一個開發環境 可以順手一起看;如果你準備把這個代理長期掛在 VPS 上,那篇 Docker 資料卷自動備份到 GitHub:適合 1Panel 的低成本容災方案 也值得補一下,因為像認證目錄、設定檔案這種東西,真出過一次問題就知道備份不是錦上添花。