# ChatGPT Web + GitHub + Self-hosted Runner：跨裝置開發，統一從 GitHub 部署

> Canonical: https://vxi.me/zh-tw/posts/chatgpt-web-github-self-hosted-runner/
> Locale: zh-tw
> Published: 2026-10-07
> Updated: 2026-10-07
> Markdown: https://vxi.me/zh-tw/posts/chatgpt-web-github-self-hosted-runner.md

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

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

```text
瀏覽器 / 本機開發 / 雲端 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 後，更像一個長時間在線的工作執行器：

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

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

```yaml
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

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

```text
ChatGPT Web
   ↓
直接修改 GitHub
```

比較準確的模型是：

```text
瀏覽器 / 手機 / 桌面
        ↓
具備寫入權限的 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 為什麼越來越像一個開發環境](/posts/what-changed-in-the-new-codex/)。

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

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

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

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

```text
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。

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

```text
/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 自動發布流程](/posts/markdown-blog-publish-workflow/) 解決的是比較小的問題：內容怎麼自動上線。這次這套架構解決的是另一層問題：不管開發從哪裡發起，哪一條路徑才有資格進正式環境。

## 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。

我更願意拆成：

```text
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 流程，可以先看 [我之前的自動發布方案](/posts/markdown-blog-publish-workflow/)；如果還在比較靜態網站和傳統 CMS，那篇 [Astro vs WordPress](/posts/astro-vs-wordpress-markdown-github-actions-blog/) 比較接近架構選擇這一層。

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

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

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

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