發佈到 App Platform 是做什麼的?
流程:在沙箱中建置與測試 → 使其可部署 → 提出計劃並獲得同意 → 推送到 GitHub → 建立 App Platform 應用程式 → 等待 ACTIVE → 檢查 URL。
在使用者批准步驟 2 的計劃之前,切勿建立儲存庫、推送或建立/更新/刪除應用程式。 像「發佈它」這樣的請求並不算是批准。在沙箱中建置與測試不需要批准。
切勿使用 apps 動作 publish、docker 或 doctl — 這些在此電腦上都無法運作。所有操作都透過兩個共享憑證進行,您需透過核心的憑證代理來呼叫它們。您永遠不會看到令牌。
每個指令都使用明確的 sandbox_id,並且對於任何呼叫代理(digitalocean、github 或 github-git)的指令,需在 execute.credentials 中選擇憑證。核心只會將 AGENT_CREDENTIAL_TOKEN 提供給這些指令。請勿使用 rm -rf;應改用 mktemp -d 建立一個新的目錄。
電腦擁有的工具
Node 24 + npm、Python 3.12 + pip、git、gh(未登入;請勿使用)、curl、gcc、make。沒有 docker、doctl、jq、wget、unzip。使用 node -e 或 python3 -c 解析 JSON。
憑證(各憑證可進行的操作)
| 憑證 | 主機 | 允許的操作 |
|---|---|---|
digitalocean | api.digitalocean.com | 在 /v2/apps 和 /v2/regions 下進行 GET/POST/PUT/DELETE |
github | api.github.com | 在 /user 和 /repos/jnaidu-do 下進行 GET/POST/PATCH |
github-git | github.com (git push/clone) | 在 /jnaidu-do/ 下進行 GET/POST |
所有內容都在共享的 GitHub 帳戶 jnaidu-do(一個部署範圍的憑證,非使用者自己的帳戶)下建立。請在計劃中說明這一點。如果使用者想要自己的帳戶,請告知他們該功能尚未設定。403 path_not_allowed / method_not_allowed 表示請求超出允許範圍 — 請不要嘗試其他變體;直接告訴使用者這是不允許的。
Broker 輔助函式
每個指令貼上此函式一次(shell 狀態不會保留)。它會將 HTTP 狀態碼輸出到 stderr,並將回應主體輸出到 stdout:
bk() { # bk <credential> <METHOD> <url> [json-body]
p=$(node -e 'const [c,m,u,b]=process.argv.slice(1);console.log(JSON.stringify({credential:c,method:m,url:u,headers:{accept:"application/json","content-type":"application/json"},...(b?{body:b}:{})}))' "$1" "$2" "$3" "${4:-}")
curl -fsS -X POST "$AGENT_API_URL/v1/credentials/broker" -H "x-agent-capability: $AGENT_CREDENTIAL_TOKEN" \
-H "content-type: application/json" -d "$p" |
node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const r=JSON.parse(s);console.error("HTTP "+r.status);console.log(r.body)})'
}
1. 使其可部署(推送前先檢查)
應用程式會以一般的容器形式在 App Platform 後方執行,而非在此沙箱中。
- 監聽
$PORT,綁定到0.0.0.0(App Platform 會設定PORT;預設為 8080)。切勿硬編碼其他連接埠。 - 純 HTML/CSS/JS 或可建置成靜態檔案的框架(Vite、CRA、Astro):以靜態網站形式部署(
build_command、output_dir)。建議以此方式優先於為靜態頁面執行伺服器。 - 任何需要伺服器的內容:使用服務形式,並設定
build_command、run_command、http_port。標準配置無需額外設定即可運作:Node 需要包含start腳本的package.json;Python 需要requirements.txt和一個啟動指令(例如gunicorn app:app --bind 0.0.0.0:$PORT)。儲存庫中的Dockerfile也可運作(需設定dockerfile_path),但您無法在此處測試建置,所以請保持簡單。 - 無持久性磁碟。 任何寫入檔案系統的內容都會在每次部署時遺失。請勿使用 SQLite 檔案、上傳到磁碟或使用 JSON 檔案來儲存狀態。如果應用程式需要資料庫,請新增一個受管理的 Postgres(請參閱下方);否則請保持無狀態。
- 先在本地執行:
npm install,使用PORT=8080啟動,並用curl localhost:8080測試。只發佈能運作的內容。 - 新增包含
node_modules等的.gitignore。請勿提交機密。切勿輸出令牌。
如果應用程式使用 Postgres
App Platform 的 ${db.DATABASE_URL} 包含 sslmode=require,而 Node 的 pg 會因為受管理的 CA 而拒絕連線(SELF_SIGNED_CERT_IN_CHAIN)。從 URL 中移除 sslmode 並設定 ssl: { rejectUnauthorized: false }:
function normalizeDatabaseUrl(raw) {
const u = new URL(raw);
u.searchParams.delete("sslmode"); u.searchParams.delete("ssl");
return u.toString();
}
const pool = new Pool({ connectionString: normalizeDatabaseUrl(process.env.DATABASE_URL), ssl: { rejectUnauthorized: false } });
在規格中加入 "databases": [{"name":"db","engine":"PG","production":false,"version":"16"}],並在需要資料庫的每個元件中加入 {"key":"DATABASE_URL","scope":"RUN_TIME","value":"${db.DATABASE_URL}"}。初次連線時請重試約 30 秒:資料庫可能仍在啟動中。只有在應用程式確實需要資料庫時才新增(會產生額外費用)。
2. 提出計劃並詢問(在此停止,直到對方同意)
一旦應用程式建置完成並可在本地執行,請停止並向使用者發送一個簡短的計劃,然後等待回覆。 在他們批准之前,請勿執行任何 bk 呼叫、git push 或建立儲存庫/應用程式。計劃應包含:
- 您建置了什麼以及它已在本地執行(一行)。
- 您將採取的步驟,按順序,每個步驟說明會觸及哪些部分:
- 建立 GitHub 儲存庫
jnaidu-do/<name>— 公開,位於共享的jnaidu-do帳戶下(非使用者自己的帳戶) - 使用 git 將應用程式的檔案推送到該儲存庫(列出包含的內容;確認無機密)
- 在
nyc區域建立 App Platform 應用程式<name>:一個靜態網站(少量無需費用)或一個服務(basic-xxs,約每月 $5)— 說明是哪一種以及費用 - 等待建置(3–5 分鐘),然後提供公開 URL
- 建立 GitHub 儲存庫
- 哪些內容會變成公開: 儲存庫和即時網站對網際網路上的任何人都可讀。
- 未經再次詢問您不會做的事: 推送後續變更、編輯規格或刪除任何內容。
最後提出一個直接的問題,例如:「我應該按照上述方式建立儲存庫和應用程式嗎?您也可以變更名稱、將儲存庫設為私人(需要授予 DO GitHub App 權限),或說不要。」如果有相關替代方案(靜態 vs 服務、不同名稱),請一併提供。如果使用者拒絕,請提供檔案或準備好程式碼的儲存庫並停止。
在進行以下操作前,請另外再詢問一次:任何後續的 git push(更新)、變更應用程式規格,以及刪除應用程式。
3. 推送到 GitHub
使用您在步驟 2 中提議的名稱(qm-<short-kebab-name>)(小寫)。如果名稱已被使用(422),請新增一個簡短的後綴。
# 建立儲存庫(公開:App Platform 之後無需額外認證即可複製)
bk github POST https://api.github.com/user/repos '{"name":"qm-hello-site","private":false}'
# 從一個包含應用程式的新乾淨目錄推送(不含 node_modules)
cd "$(mktemp -d)" && cp -r /path/to/app/. . && git init -q -b main && git add . &&
git -c user.name=QM -c [email protected] commit -qm "Initial commit" &&
git remote add origin "$AGENT_API_URL/v1/credentials/git/github-git/jnaidu-do/qm-hello-site.git" &&
git -c http.extraHeader="x-agent-capability: $AGENT_CREDENTIAL_TOKEN" push origin HEAD:refs/heads/main
後續更新:以相同方式提交並推送(HEAD:refs/heads/main),但只有在告知使用者變更內容並獲得同意之後 — 每次推送都需要新的批准,原始計劃不涵蓋後續推送。
4. 建立 App Platform 應用程式
bk digitalocean POST https://api.digitalocean.com/v2/apps '{"spec": {
"name": "qm-hello-site",
"region": "nyc",
"services": [{
"name": "web",
"github": {"repo": "jnaidu-do/qm-hello-site", "branch": "main", "deploy_on_push": true},
"build_command": "npm install",
"run_command": "npm start",
"http_port": 8080,
"instance_size_slug": "basic-xxs",
"instance_count": 1
}]
}}'
靜態網站替代 services:使用 "static_sites": [{"name":"web","github":{...},"build_command":"npm run build","output_dir":"dist"}](純 HTML:省略 build_command,output_dir 為 "/";對於單頁應用程式,請新增 "catchall_document":"index.html")。
回應中包含應用程式的 id。請保留它。應用程式名稱由小寫字母、數字、連字號組成,長度 2–32 個字元。
如果建立失敗並出現 GitHub 儲存庫存取錯誤(「無法存取該儲存庫」):表示 DigitalOcean GitHub App 未授予此儲存庫的存取權限。請告訴使用者開啟 https://github.com/apps/digitalocean/installations/select_target 並授予對 jnaidu-do 儲存庫的存取權限,然後告知完成;重新嘗試同一個請求。如果使用者無法做到,請回退到通用的公開複製來源("git": {"repo_clone_url": "https://github.com/jnaidu-do/qm-hello-site.git", "branch": "main"}),並告知使用者後續更新需要重新部署(請參閱下方)。
5. 等待 ACTIVE
實際建置需要 3–5 分鐘。每 30 秒輪詢一次,並告知使用者您正在等待:
bk digitalocean GET "https://api.digitalocean.com/v2/apps/$APP_ID/deployments?per_page=1" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const d=JSON.parse(s).deployments[0];console.log(d.id,d.phase,d.progress?.success_steps+"/"+d.progress?.total_steps)})'
階段:PENDING_BUILD → BUILDING → DEPLOYING → ACTIVE。ERROR 或 CANCELED 表示失敗;請前往「失敗時」。大約 12 分鐘後停止輪詢,並告知使用者目前的狀態。
6. 回報 URL
bk digitalocean GET "https://api.digitalocean.com/v2/apps/$APP_ID" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>console.log(JSON.parse(s).app.live_url))'
嘗試 curl -s -o /dev/null -w '%{http_code}' <live_url> — 預期得到 200。此電腦通常無法連線到新的 *.ondigitalocean.app 網站(連線失敗)。如果 curl 失敗但部署狀態為 ACTIVE,這不是部署失敗:請告知您無法從此處驗證,並請使用者開啟該 URL。提供使用者完整的 https://…ondigitalocean.app URL 和儲存庫 URL。該應用程式在網際網路上是公開的。
失敗時
請自行閱讀日誌;不要要求使用者提供。先取得日誌 URL,然後擷取它:
bk digitalocean GET "https://api.digitalocean.com/v2/apps/$APP_ID/deployments/$DEPLOYMENT_ID/logs?type=BUILD"
# 主體中包含 "live_url"(或 "historic_urls");使用 curl 取得該 URL(無需認證)
type 可以是 BUILD、DEPLOY 或 RUN(靜態網站只有 BUILD)。比對信號:
| 信號 | 修正方式 |
|---|---|
Missing script: start / 找不到指令 | 錯誤的 build_command / run_command,或缺少 start 腳本 |
| 容器退出,健康檢查失敗 | 未監聽 $PORT / 未綁定到 0.0.0.0,或啟動時崩潰 |
SELF_SIGNED_CERT_IN_CHAIN | 缺少 Postgres SSL 輔助函式(請參閱上方) |
Cannot find module | package.json 中缺少相依套件,或提交的 node_modules 不一致 |
| Vite "Blocked request. This host is not allowed" | 設定 preview: { allowedHosts: true }(或將建置好的 dist 作為靜態網站提供服務) |
| 建置「已跳過」/ 數秒內變為 ACTIVE | 原始碼未變更;推送一個真正的提交 |
告訴使用者根本原因(確切的日誌行)以及您建議的修正方式,並在推送修正或變更規格前先詢問 — 每次推送和每次規格變更都需要各自的同意。在使用者批准後,修正程式碼、提交並推送(deploy_on_push 會自動重新部署),然後再次輪詢。若要變更規格,請使用 PUT /v2/apps/$APP_ID 並附上 {"spec": {...}}(先用 GET /v2/apps/$APP_ID 取得目前規格再編輯)。對於一般的 git 來源,使用 POST /v2/apps/$APP_ID/deployments 並附上主體 {"force_build":true} 來強制重建。請勿透過刪除並重建應用程式來修復失敗 — 這會變更 URL。請向前修正。三次不同的失敗嘗試後放棄,並回報您嘗試過的內容。讀取日誌不需要批准。
刪除
僅在使用者明確要求時,並先說出應用程式名稱。DELETE /v2/apps/$APP_ID 會移除應用程式。GitHub 儲存庫會保留下來(此憑證無法刪除儲存庫);請告知使用者。
報告
結束時提供:即時 URL、儲存庫 URL,以及一行說明如何更新(「請告訴我要變更 X,我會推送它」)。