ChatGPT Web + GitHub + Self-hosted Runner:どの端末からでも開発し、GitHubから統一デプロイする
以前の私のserver projectは、開発からreleaseまでほぼ1台のPCに依存していました。localでrepositoryをpullし、CodexやIDEで変更して git push、terminalからSSHでserverへ入り、git pull、build、restart、log確認。単純で分かりやすいworkflowです。
ただ、projectを長く保守し始めると別の問題が出ます。開発環境、local checkout、SSH session、production releaseが全部その1台のPCに結び付いてしまいます。
最近、自分のblog deploymentを整理し直し、productionの流れを次の形まで絞りました。
browser / local development / cloud coding agent
↓
GitHub
↓
GitHub Actions
↓
Self-hosted Runner
↓
build / validate
↓
release directory
↓
current symlink
↓
production
この構成で一番重要なのは「local PCが不要になる」ことではありません。開発の入口は変えられても、productionへの出口はできるだけ1本に保つことです。Macで作業しても、mobileでtaskを確認しても、browserからcloud coding taskを続けても構いません。長く残したいのはGitHub上のcode state、review history、そしてdeterministicなproduction workflowです。
今回、元のメモを記事にする前にGitHubとOpenAIの現行documentation、さらに実際に動いている自分のworkflowを見直しました。その結果、2つの表現を狭める必要がありました。通常のChatGPT GitHub connectionはrepositoryへのwrite権限そのものではありません。また、現在のdeploymentも「どんなfailureでも自動rollbackする」仕組みではありません。
GitHubの現行Self-hosted Runner referenceはこちらです:https://docs.github.com/en/actions/reference/runners/self-hosted-runners
Self-hosted RunnerはGitHubがserverへSSHする仕組みではない
最初は「pushを受けたGitHubがVPSへremote loginしてscriptを実行する」と考えがちです。実際のmodelは違います。
GitHubの公式documentationでは、Self-hosted Runner applicationは自分のmachine上で動き、Runner側からGitHubへoutbound connectionを張ってjob assignmentを受け取ります。hostはoutbound HTTPSへ接続できる必要があり、中心となる通信はport 443です。
VPSをRunnerとして登録すると、イメージとしては常時onlineのworkerに近くなります。
GitHub
↑ Runnerがconnectionを確立
│
Self-hosted Runner on VPS
│
├─ checkout
├─ npm ci
├─ test / build
└─ local deploy
workflowで「どのworkerがjobを受け取るか」を決めるのはrunner labelです。公開用に単純化すると、私は次のように考えています。
jobs:
deploy:
runs-on: [self-hosted, linux, prod]
これは以前のSSH/SCP deploymentとはかなり違います。external CIでbuildする場合は、artifactをVPSへ転送する仕組みが別に必要です。RunnerがVPS上にいれば、job自体がそのmachineで実行され、build後はlocal deploy scriptを直接呼べます。CIからserverへ入るSSH key、SCP path、remote shell commandをもう一段管理しなくて済みます。
私にとってGitHubはcontrol planeとdurable state、VPSはexecution planeです。信頼できるRunnerがonlineであれば、どのdeviceからcode changeを始めたかより、最終的に同じworkflowを通ることの方が重要です。
その結果、repositoryを本当のsource of truthとして扱いやすくなりました。Runner workspaceは「server上のmain code」ではなく、1つのjobのための一時的なexecution workspaceです。長く追うべきものはcommit、PR、workflow run、releaseです。
Browserを開発入口にはできるが、「GitHubと接続済み」と「GitHubへ書ける」は別
ここは製品説明だけを見ていると混同しやすい部分です。
OpenAIの現在のGitHub connection documentationでは、通常のChatGPT GitHub appとwrite-capableなcoding workflowが明確に分かれています。標準のconnectionは、許可したrepositoryを読む、searchする、analyzeする用途です。Help Centerも、GitHubへ直接codeを生成・編集・pushする場合はCodexを使う、と説明しています。
公式説明:https://help.openai.com/en/articles/11145903-connecting-github-to-chatgpt
だからarchitectureを次のようには描きません。
ChatGPT Web
↓
GitHubを直接編集
より正確にはこうです。
browser / phone / desktop
↓
write-capable coding workflow
Codex Cloud / controlled agent
↓
branch / commit / PR
↓
GitHub
Codex Cloudがあることで、「手元のPCがなくても作業を続ける」が現実的になります。OpenAIの現行documentationでは、cloud taskはOpenAI管理のcomputer上で動き、対応するdesktop、web、mobile surfaceから開始・継続できます。local PCがsleepしていてもtaskは続行できます。
参考:https://help.openai.com/en/articles/11369540-using-codex-with-your-chatgpt-plan
ただし、これを「web版はtoken limitがない」「usage limitが存在しない」とは書きません。plan、workspace policy、product limitは変わります。長く使える本質的な利点はもっと単純で、code stateを1台のMacのterminal sessionだけに置かなくてよくなることです。
その意味で、今の私はCodexを「1つのmodel window」より「開発入口の1つ」と考えています。deviceを切り替えても作業を継続できるのは、repository、environment、task state、verification chainが残るからです。
この変化については、以前の 新しいCodexが開発環境に近づいている理由 でも書きました。
Productionへの出口をGitHub 1本にしてから、「どの端末からでも開発」を信用できるようになった
browserでcodeを変更できるだけでは、私にはまだ不十分でした。最後のreleaseで「Macに戻ってSSHし、server上でcommandを実行する」必要があるなら、production dependencyはlocal PCに残ったままです。
今のblog setupで重要なのは、productionが信頼された main updateだけを1つのworkflowから受け取ることです。
現在動いているworkflowを確認すると、大まかな順序は次の通りです。
main push
↓
checkout
↓
runtime / directory checks
↓
npm ci
↓
syntax checks
↓
build
↓
capture previous release
↓
local atomic deploy
↓
post-deploy SEO verification
↓
submit sitemap
これはdiagramだけではありません。2026年10月7日に完了済みのproduction Actions runを確認し、Build、Atomic Deploy、Post-Deploy SEO Verification、sitemap submissionがすべて成功していました。私にとって重要なのは、production releaseが本当に「あるlocal PCのSSH操作」からrepository workflowへ移ったことです。
deploy scriptのlayoutもかなり単純です。
/srv/app/
├─ releases/
│ ├─ 20261007-abc1234/
│ └─ 20261007-def5678/
└─ current -> releases/20261007-def5678/
新しいbuildは独立したrelease directoryへ先に入ります。その後temporary symlinkを current としてatomicに切り替えます。NginxなどのWeb Serverはtransientなbuild workspaceではなく、常に current を見るだけです。
元のメモには「失敗したら自動rollback」と書きたくなる部分もありました。しかし実装を読むと、正確なboundaryはもう少し狭いです。
- buildやpre-deploy validationで失敗すれば、production switch自体が行われない;
- deploy後のSEO verificationが失敗すれば、workflowには
currentをprevious releaseへ戻す明示的な処理がある; - だからといってsymlink切り替え後のすべてのfailureにautomatic rollbackがある、とは言えない。
細かい差に見えますが、operationsでは大事です。architecture diagramが実装より強く見える状態は避けたい。将来recoveryを強化するなら、failure classごとに明示的に実装すべきで、「atomic deploy」という言葉だけに安心を任せない方がいいです。
以前の Markdown + GitHub Actions publishing workflow は「contentをどう自動で公開するか」という小さい問題を扱っていました。今回の構成は「開発をどこから始めても、productionへ到達できる経路は何か」を整理したものです。
Self-hosted Runnerの最大の罠は、本当に自分のmachineでcodeを実行すること
RunnerをVPSに置くと便利なのは、jobがlocal filesystem、network、deploy pathへ直接アクセスできるからです。同時に、それがriskでもあります。
GitHubのsecurity documentationはかなり明確です。Self-hosted RunnerはGitHub-hosted Runnerのように、jobごとに新しいdisposable VMになる保証がありません。不正なworkflow codeはhostをpersistentにcompromiseできる可能性があります。そのためGitHubはpublic repositoryでのSelf-hosted Runner利用を強く避けるよう案内し、private/internal repositoryでもforkやPRの経路に注意するよう説明しています。
公式security guidance:https://docs.github.com/en/actions/reference/security/secure-use
これを読んで、自分のworkflowも見直しました。production deploymentは信頼した main pushで起動するので、このboundaryは維持したい。一方、現在のcontent PR validationも同じSelf-hosted Runnerで動きます。
今のようにprivate repositoryで、authorも信頼できる範囲ならriskは管理できます。ただ、将来repositoryをpublicにしたり、より広いcontributorからPRを受け取るなら、PR codeをproduction runnerでは実行しません。
その場合は次のように分けたいです。
untrusted / public PR
↓
GitHub-hosted runner
またはisolated disposable validation runner
protected main
↓
production Self-hosted Runner
↓
deploy
teamが大きくなれば、production Runner groupも必要なrepositoryとworkflowだけに限定した方がいいでしょう。
「AIにVPS SSH keyを渡さなくてよい」は良い改善ですが、それだけでsecurity boundaryが完成するわけではありません。Agentがworkflowを変更でき、そのworkflowがproduction host上で任意のshellを実行できるなら、privilegeは消えたのではなく入口を変えただけです。
守るべきなのは、誰がworkflowを変更できるか、誰が main へmergeできるか、どのeventがproduction runnerへ届くか、runner userがどこへwriteできるか、Secretsにどこまで権限を持たせるか、という全体です。
Local CodexとSSHは消えたのではなく、より適した役割へ移った
この構成にしても、local Codex、local checkout、SSHが古くなるわけではありません。
browser control planeが苦手なのは、今この瞬間のmachine stateです。
私は今、役割をこう分けています。
| シーン | Cloud / GitHub workflow | Local Codex + SSH |
|---|---|---|
| deviceをまたいでcoding taskを続ける | 向いている | 現在のmachineに依存 |
| code history / PR review | 向いている | GitHubがcanonical |
| 標準build / test / deploy | 向いている | 毎回manualにしない |
docker ps / journalctl 確認 | 間接的 | 向いている |
| database / disk / network調査 | 間接的 | 向いている |
| production緊急対応 | 補助可能 | SSHが直接的 |
| audit可能なrelease | runとcommitが残る | manual SSHは弱い |
browserやphoneはcontrol planeとして使いやすいです。何を変えるか決める、diffを見る、CIを確認する、定義済みworkflowをtriggerする。一方local terminalはobservability/debug planeに近いです。今どのprocessが止まっているか、どのcontainerがrestartしているか、diskがなぜfullなのか、Nginxがどのconfigを実際に読んでいるかを見る場所です。
SSHも削除する必要はありません。私はbreak-glass pathへ降格させる方が自然だと思っています。日常releaseはSSHに依存しない。productionが壊れたときは直接入れる。ただし緊急時にserverで行ったfixも、最終的にはGitHubへcodeまたはworkflow stateとして戻し、serverだけに存在する変更を残さないようにします。
すでにlocal Markdown + GitHub Actionsを使っているなら、まず 以前のpublishing workflow が単純な出発点です。static siteとtraditional CMSを比較している段階なら、Astro vs WordPress の方が近いでしょう。
今の私が重視しているのは、この4つの役割を分けることです。
browser / Codex / local IDE → development entry
GitHub → durable code + review state
Self-hosted Runner → trusted automated execution
SSH → deep diagnosis + emergency access
deviceは変えられます。Agentも変えられます。production serverも将来交換できます。code state、deployment rule、verification logicがGitHubに残っていれば、workflow全体が1台のPCと一緒に消えることはありません。
私がChatGPT/Codex + GitHub + Self-hosted Runnerに価値を感じるのはここです。terminalを隠すことではなく、「この作業は必ずこの1台のPCで手動実行しなければならない」という工程を、通常運用からできるだけ減らすことです。