以前我做伺服器專案,開發和發布幾乎都繞不開同一台電腦:本機拉程式碼,用 Codex 或 IDE 修改,git push,接著開終端機 SSH 到伺服器,git pull、建置、重啟、看日誌。這種方式沒有什麼問題,甚至很直覺;真正麻煩的是專案開始長期維護之後,開發環境、本機工作目錄、SSH 工作階段和正式發布會一起綁在同一台機器上。

最近我把自己的部落格部署鏈路重新收斂,最後保留下來的正式流程是:

瀏覽器 / 本機開發 / 雲端 Coding Agent
                  ↓
                GitHub
                  ↓
          GitHub Actions
                  ↓
        Self-hosted Runner
                  ↓
         build / validate
                  ↓
          release directory
                  ↓
          current symlink
                  ↓
              production

我真正喜歡的不是「以後完全不用本機電腦」,而是開發入口可以改變,正式環境的出口盡量只保留一條。Mac 上寫程式、手機上看任務、瀏覽器裡繼續一個雲端 Coding 任務,都只是不同入口;真正需要長期穩定的是 GitHub 裡的程式碼狀態、審查紀錄和那條確定性的正式 workflow。

把筆記整理成文章時,我也重新對照了 GitHub、OpenAI 的目前文件,以及自己倉庫真正執行的 workflow。有兩個說法必須收緊:普通 ChatGPT 連上 GitHub,不等於自動擁有寫入儲存庫的權限;我現在這套部署也不能簡化成「任何失敗都會自動回滾」。

GitHub 目前對 Self-hosted Runner 的說明在這裡:https://docs.github.com/en/actions/reference/runners/self-hosted-runners

Self-hosted Runner 不是 GitHub 遠端 SSH 進你的伺服器

一開始很容易把它想成「GitHub 收到 push,再遠端登入 VPS 執行腳本」。實際模型不是這樣。

GitHub 官方文件寫得很清楚:Self-hosted Runner 應用程式跑在你自己的機器上,由 Runner 主動連線 GitHub 接收 job assignment;主機需要能向 GitHub 發起 outbound HTTPS 連線,核心通訊走 443 連接埠。

一台 VPS 註冊成 Runner 後,更像一個長時間在線的工作執行器:

GitHub
  ↑  Runner 主動建立連線
  │
Self-hosted Runner on VPS
  │
  ├─ checkout
  ├─ npm ci
  ├─ test / build
  └─ local deploy

workflow 真正用來決定「誰來接這份工作」的是 runner label。公開文章裡,我把自己的標籤簡化成:

jobs:
  deploy:
    runs-on: [self-hosted, linux, prod]

這和舊的 SSH/SCP 發布方式差很多。以前 CI 在外部機器建置,之後還要把產物推進 VPS;現在 job 本身就在 VPS 的 Runner workspace 裡執行,建置完成後可以直接呼叫本機部署腳本,不需要再多維護一層 CI 到伺服器的 SSH key、SCP 路徑和遠端 shell 命令。

對我來說,GitHub 比較像控制平面和持久狀態中心,VPS 才是執行平面。只要受信任的 Runner 在線,究竟是哪個裝置發起程式碼修改就沒那麼重要,正式環境最終都會經過同一份 workflow。

這也讓我更願意把 GitHub 當成 source of truth。Runner workspace 不再是「伺服器上的主程式碼」,而只是某一次 job 的暫時執行目錄;真正需要追蹤的是 commit、PR、workflow run 和 release。

瀏覽器可以成為開發入口,但「連上 GitHub」不等於「可以寫 GitHub」

這一層最容易被產品宣傳語混在一起。

OpenAI 目前的 GitHub 連線文件,明確把普通 ChatGPT GitHub app 和具備寫入能力的 Coding workflow 分開。一般連線用途是讀取、搜尋和分析你授權的儲存庫;Help Center 也直接說明,若要生成、編輯並直接 push 程式碼到 GitHub,需要走 Codex 這類具備寫入權限的流程。

官方說明:https://help.openai.com/en/articles/11145903-connecting-github-to-chatgpt

所以我不會再把架構畫成:

ChatGPT Web
   ↓
直接修改 GitHub

比較準確的模型是:

瀏覽器 / 手機 / 桌面
        ↓
具備寫入權限的 Coding workflow
Codex Cloud / 受控 Agent
        ↓
branch / commit / PR
        ↓
GitHub

Codex Cloud 才真正讓「電腦不在身邊也能繼續開發」變得實用。OpenAI 目前文件說明,雲端任務會跑在 OpenAI 管理的電腦上,可以從支援的桌面、網頁或行動端繼續;本機電腦休眠後,雲端任務仍可以繼續執行。

對應說明:https://help.openai.com/en/articles/11369540-using-codex-with-your-chatgpt-plan

但我不會把這件事寫成「網頁版沒有 Token 限制」或「永遠沒有使用額度」。方案、工作區政策和產品限制都可能改變。真正穩定的優勢比較簡單:程式碼狀態不再依賴當下這一台 Mac 的終端機工作階段。

這也是我後來為什麼更願意把 Codex 看成一個開發入口,而不是一個模型視窗。真正讓工作可以換裝置繼續的,是儲存庫、環境、任務狀態和驗證鏈。

如果你想繼續看這個方向,我之前也寫過 新版 Codex 為什麼越來越像一個開發環境。

正式環境只剩 GitHub 一個出口後,我才開始真正相信「跨裝置開發」

只做到「瀏覽器裡能改程式碼」還不夠。如果最後發布仍然要「回 Mac、SSH 進伺服器再敲幾條命令」,那只是把寫程式這一段搬到雲端,正式環境的依賴仍然綁在本機。

我現在這套部落格 workflow 更重要的一點,是正式環境只接受受信任的 main 更新。

我重新檢查了目前真正執行的 workflow,大致順序是:

main push
  ↓
checkout
  ↓
runtime / directory checks
  ↓
npm ci
  ↓
syntax checks
  ↓
build
  ↓
capture previous release
  ↓
local atomic deploy
  ↓
post-deploy SEO verification
  ↓
submit sitemap

這不只是架構圖。2026 年 10 月 7 日,我核對了一次已經完成的正式 Actions run,Build、Atomic Deploy、Post-Deploy SEO Verification 和 sitemap submission 都成功結束。對我來說,這證明發布真的已經從「某台本機電腦上的 SSH 操作」搬進 repository workflow。

部署腳本本身也很簡單。公開示例可以抽象成:

/srv/app/
├─ releases/
│  ├─ 20261007-abc1234/
│  └─ 20261007-def5678/
└─ current -> releases/20261007-def5678/

新版本先進入獨立的 release 目錄,再用暫時 symlink 原子替換 current。Nginx 或其他 Web Server 一直只需要指向 current,不會直接吃某個瞬時 build workspace。

我也順手把自己原本的一句話修正得更精確。原先很容易把這套設計概括成「失敗就自動回滾」,但真正看 workflow 後,實際邊界是:

  • build 或部署前檢查失敗時,根本不會切換正式版本;
  • 部署後 SEO verification 失敗時,workflow 有明確邏輯把 current 指回 previous release;
  • 這不代表所有發生在 symlink 切換之後的故障都有自動回滾路徑。

這個差別看起來有點吹毛求疵,但在運維上很重要。漂亮的架構圖很容易比實際程式碼更強。如果之後真的要擴大自動恢復能力,就應該把 failure class 一個個定義出來,而不是只靠「atomic deploy」幾個字產生安全感。

我之前那篇 Markdown + GitHub Actions 自動發布流程 解決的是比較小的問題:內容怎麼自動上線。這次這套架構解決的是另一層問題:不管開發從哪裡發起,哪一條路徑才有資格進正式環境。

Self-hosted Runner 最大的坑,是它真的會在你的機器上執行程式碼

把 Runner 放在 VPS 上很方便,正是因為 job 能碰到本機檔案系統、網路和部署目錄。風險也正好來自同一件事。

GitHub 的安全文件講得非常直接:Self-hosted Runner 不像 GitHub-hosted Runner 一樣,保證每個 job 都是全新、可丟棄的暫時虛擬機器。不受信任的 workflow code 可能持久污染主機。因此 GitHub 強烈不建議在 public repository 上使用 Self-hosted Runner,也提醒 private/internal repo 仍然要仔細看 fork/PR 路徑。

官方安全說明:https://docs.github.com/en/actions/reference/security/secure-use

看到這裡,我又重新檢查了一遍自己的 workflow。正式部署由受信任的 main push 觸發,這個方向沒問題;但目前內容 PR 的驗證任務也會跑在同一台 Self-hosted Runner。

在我現在這種私人儲存庫、受信任作者的使用模型裡,風險還是可控的。可是一旦未來把儲存庫公開,或讓更多外部協作者提交 PR,我不會再讓「PR 裡的任意程式碼」碰到正式 Runner。

我更願意拆成:

Untrusted / public PR
        ↓
GitHub-hosted runner
或獨立、可銷毀的 validation Runner

Protected main
        ↓
Production self-hosted runner
        ↓
Deploy

團隊再大一點時,Production Runner group 也應該只允許真正需要它的 repository 和 workflow 使用。

這也是為什麼「AI 不再需要 VPS SSH key」只算安全改善的一部分。拿掉 Agent 的直接 SSH 憑據很好,但如果 Agent 能修改 workflow,而 workflow 可以在正式主機上執行任意 shell,那權限只是換了一個入口,並沒有消失。

真正要保護的是整個執行邊界:誰可以修改 workflow、誰可以 merge 到 main、哪些事件能命中 Production Runner、Runner 使用者可以寫哪些目錄、Secrets 到底有多大權限。

本機 Codex 和 SSH 沒有消失,只是退回更適合的位置

做到這裡之後,我反而不覺得本機 Codex、本機儲存庫和 SSH 變落後了。

它們處理的是瀏覽器控制平面最不擅長的東西:即時的機器狀態。

我現在會這樣分:

情境雲端 / GitHub workflow本機 Codex + SSH
跨裝置繼續 Coding 任務很適合依賴目前這台機器
程式碼歷史與 PR 審查很適合仍以 GitHub 為準
標準 build / test / deploy很適合不該每次手動做
查看 docker ps / journalctl間接很適合
資料庫、磁碟、網路現場排查間接很適合
正式環境緊急處理可協助SSH 更直接
可稽核發布天生有 run / commit手動 SSH 較弱

瀏覽器或手機很適合當 control plane:決定要改什麼、看 diff、審查 CI、觸發已經定義好的 workflow。本機終端機更像 observability/debug plane:現在到底是哪個 process 卡住、哪個 container 一直重啟、磁碟為什麼滿、Nginx 實際載入的是哪份設定。

SSH 也不需要完全移除。我更願意把它降級成 break-glass 通道:日常發布不依賴它,真正遇到正式故障時仍然可以直接進機器。臨時在伺服器上做的修復,最後也應該回到 GitHub 形成 commit 或 workflow 狀態,而不是讓機器長期累積只有某台電腦知道的現場修改。

如果你已經有一套本機 Markdown + GitHub Actions 流程,可以先看 我之前的自動發布方案;如果還在比較靜態網站和傳統 CMS,那篇 Astro vs WordPress 比較接近架構選擇這一層。

我現在更在意的是這四個角色終於可以分開:

瀏覽器 / Codex / 本機 IDE   → 開發入口
GitHub                     → 持久程式碼與審查狀態
Self-hosted Runner         → 受信任的自動執行
SSH                        → 深度診斷與緊急通道

裝置可以換,Agent 可以換,甚至正式伺服器以後也可以換。只要程式碼狀態、部署規則和驗證邏輯都留在 GitHub,整條鏈路就不會跟著某一台電腦一起消失。

這才是我覺得 ChatGPT/Codex + GitHub + Self-hosted Runner 最有價值的地方:不是把終端機藏起來,而是盡量把「這件事一定要在這台電腦上親手完成」從日常流程裡拿掉。